@solongate/proxy 0.48.0 → 0.49.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/audit/checks/asi01-goal-hijacking.d.ts +2 -0
- package/dist/audit/checks/asi02-tool-misuse.d.ts +2 -0
- package/dist/audit/checks/asi03-identity-abuse.d.ts +2 -0
- package/dist/audit/checks/asi04-supply-chain.d.ts +2 -0
- package/dist/audit/checks/asi05-code-execution.d.ts +2 -0
- package/dist/audit/checks/asi06-memory-poisoning.d.ts +2 -0
- package/dist/audit/checks/asi07-inter-agent.d.ts +2 -0
- package/dist/audit/checks/asi08-cascading-failures.d.ts +2 -0
- package/dist/audit/checks/asi09-human-trust.d.ts +2 -0
- package/dist/audit/checks/asi10-rogue-agents.d.ts +2 -0
- package/dist/audit/checks/baseline.d.ts +7 -0
- package/dist/audit/checks/chain-analysis.d.ts +2 -0
- package/dist/audit/checks/data-flow.d.ts +2 -0
- package/dist/audit/checks/index.d.ts +2 -0
- package/dist/audit/collector.d.ts +2 -0
- package/dist/audit/config.d.ts +9 -0
- package/dist/audit/export.d.ts +12 -0
- package/dist/audit/index.d.ts +2 -0
- package/dist/audit/index.js +3426 -0
- package/dist/audit/reporter.d.ts +22 -0
- package/dist/audit/spinner.d.ts +10 -0
- package/dist/audit/types.d.ts +105 -0
- package/dist/cli-utils.d.ts +27 -0
- package/dist/config.d.ts +105 -0
- package/dist/core/capability-token.d.ts +46 -0
- package/dist/core/constants.d.ts +47 -0
- package/dist/core/context-boundary.d.ts +19 -0
- package/dist/core/context.d.ts +24 -0
- package/dist/core/errors.d.ts +51 -0
- package/dist/core/execution.d.ts +35 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/input-guard.d.ts +66 -0
- package/dist/core/mcp-types.d.ts +35 -0
- package/dist/core/permissions.d.ts +27 -0
- package/dist/core/policy.d.ts +399 -0
- package/dist/core/response-scanner.d.ts +27 -0
- package/dist/core/schema-validator.d.ts +33 -0
- package/dist/core/tool.d.ts +23 -0
- package/dist/core/trust.d.ts +28 -0
- package/dist/create.d.ts +13 -0
- package/dist/global-install.d.ts +17 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +211 -158
- package/dist/init.d.ts +18 -0
- package/dist/inject.d.ts +19 -0
- package/dist/lib.d.ts +18 -0
- package/dist/lib.js +103 -48
- package/dist/login.d.ts +2 -0
- package/dist/policy-engine/command-matcher.d.ts +34 -0
- package/dist/policy-engine/defaults.d.ts +23 -0
- package/dist/policy-engine/engine.d.ts +30 -0
- package/dist/policy-engine/evaluator.d.ts +13 -0
- package/dist/policy-engine/filename-matcher.d.ts +20 -0
- package/dist/policy-engine/index.d.ts +12 -0
- package/dist/policy-engine/matcher.d.ts +18 -0
- package/dist/policy-engine/opa/index.d.ts +4 -0
- package/dist/policy-engine/opa/json-to-rego.d.ts +2 -0
- package/dist/policy-engine/opa/opa-evaluator.d.ts +10 -0
- package/dist/policy-engine/opa/rego-compiler.d.ts +2 -0
- package/dist/policy-engine/opa/request-adapter.d.ts +17 -0
- package/dist/policy-engine/path-matcher.d.ts +41 -0
- package/dist/policy-engine/policy-store.d.ts +66 -0
- package/dist/policy-engine/url-matcher.d.ts +29 -0
- package/dist/policy-engine/validator.d.ts +7 -0
- package/dist/policy-engine/warnings.d.ts +10 -0
- package/dist/proxy.d.ts +89 -0
- package/dist/pull-push.d.ts +17 -0
- package/dist/sdk/config.d.ts +26 -0
- package/dist/sdk/expiring-set.d.ts +18 -0
- package/dist/sdk/index.d.ts +10 -0
- package/dist/sdk/interceptor.d.ts +45 -0
- package/dist/sdk/logger.d.ts +16 -0
- package/dist/sdk/rate-limiter.d.ts +59 -0
- package/dist/sdk/secure-server.d.ts +68 -0
- package/dist/sdk/server-verifier.d.ts +53 -0
- package/dist/sdk/solongate.d.ts +105 -0
- package/dist/sdk/token-issuer.d.ts +37 -0
- package/dist/sync.d.ts +82 -0
- package/package.json +8 -4
|
@@ -0,0 +1,3426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/audit/collector.ts
|
|
10
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
11
|
+
import { resolve as resolve2, join as join2 } from "path";
|
|
12
|
+
import { homedir as homedir2 } from "os";
|
|
13
|
+
|
|
14
|
+
// src/audit/config.ts
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
16
|
+
import { resolve, join } from "path";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
var CONFIG_DIR = resolve(homedir(), ".solongate-audit");
|
|
19
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
20
|
+
function ensureConfigDir() {
|
|
21
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
22
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function loadConfig() {
|
|
26
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
27
|
+
return { customDirs: [] };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return { customDirs: [] };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function saveConfig(config) {
|
|
36
|
+
ensureConfigDir();
|
|
37
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
38
|
+
}
|
|
39
|
+
function addDir(dir) {
|
|
40
|
+
const absDir = resolve(dir);
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
if (config.customDirs.includes(absDir)) {
|
|
43
|
+
console.log(` Already added: ${absDir}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!existsSync(absDir)) {
|
|
47
|
+
console.log(` Warning: directory does not exist: ${absDir}`);
|
|
48
|
+
console.log(` Adding anyway \u2014 it may appear later.
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
config.customDirs.push(absDir);
|
|
52
|
+
saveConfig(config);
|
|
53
|
+
console.log(` Added: ${absDir}`);
|
|
54
|
+
console.log(` Total custom dirs: ${config.customDirs.length}
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
function removeDir(dir) {
|
|
58
|
+
const absDir = resolve(dir);
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
const idx = config.customDirs.indexOf(absDir);
|
|
61
|
+
if (idx === -1) {
|
|
62
|
+
const match = config.customDirs.find((d) => d.includes(dir));
|
|
63
|
+
if (match) {
|
|
64
|
+
config.customDirs.splice(config.customDirs.indexOf(match), 1);
|
|
65
|
+
saveConfig(config);
|
|
66
|
+
console.log(` Removed: ${match}`);
|
|
67
|
+
console.log(` Remaining: ${config.customDirs.length}
|
|
68
|
+
`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(` Not found: ${absDir}`);
|
|
72
|
+
console.log(` Use --list-dirs to see saved directories.
|
|
73
|
+
`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
config.customDirs.splice(idx, 1);
|
|
77
|
+
saveConfig(config);
|
|
78
|
+
console.log(` Removed: ${absDir}`);
|
|
79
|
+
console.log(` Remaining: ${config.customDirs.length}
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
function listDirs() {
|
|
83
|
+
const config = loadConfig();
|
|
84
|
+
const home = homedir();
|
|
85
|
+
console.log("\n Default log directories:");
|
|
86
|
+
console.log(` Claude Code \u2192 ${resolve(home, ".claude", "projects")}`);
|
|
87
|
+
console.log(` Gemini CLI \u2192 ${resolve(home, ".gemini", "tmp")}`);
|
|
88
|
+
console.log(` OpenClaw \u2192 ${resolve(home, ".openclaw", "agents", "main", "sessions")}`);
|
|
89
|
+
if (config.customDirs.length === 0) {
|
|
90
|
+
console.log("\n Custom directories: (none)");
|
|
91
|
+
} else {
|
|
92
|
+
console.log(`
|
|
93
|
+
Custom directories (${config.customDirs.length}):`);
|
|
94
|
+
for (const d of config.customDirs) {
|
|
95
|
+
const exists = existsSync(d);
|
|
96
|
+
console.log(` ${exists ? "+" : "-"} ${d}${exists ? "" : " (not found)"}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
console.log(`
|
|
100
|
+
Config: ${CONFIG_FILE}
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
function searchLogs() {
|
|
104
|
+
const home = homedir();
|
|
105
|
+
const found = [];
|
|
106
|
+
console.log("\n Searching for AI tool logs...\n");
|
|
107
|
+
const defaults = [
|
|
108
|
+
{ name: "Claude Code", path: resolve(home, ".claude", "projects") },
|
|
109
|
+
{ name: "Gemini CLI", path: resolve(home, ".gemini", "tmp") },
|
|
110
|
+
{ name: "OpenClaw", path: resolve(home, ".openclaw", "agents", "main", "sessions") }
|
|
111
|
+
];
|
|
112
|
+
for (const d of defaults) {
|
|
113
|
+
if (existsSync(d.path)) {
|
|
114
|
+
console.log(` Found ${d.name}: ${d.path}`);
|
|
115
|
+
found.push(d.path);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (process.platform === "win32") {
|
|
119
|
+
const usersDir = resolve(home, "..");
|
|
120
|
+
try {
|
|
121
|
+
const { readdirSync: readdirSync2, statSync: statSync2 } = __require("fs");
|
|
122
|
+
for (const user of readdirSync2(usersDir)) {
|
|
123
|
+
const userHome = join(usersDir, user);
|
|
124
|
+
if (userHome === home) continue;
|
|
125
|
+
try {
|
|
126
|
+
if (!statSync2(userHome).isDirectory()) continue;
|
|
127
|
+
} catch {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const otherPaths = [
|
|
131
|
+
{ name: `Claude Code (${user})`, path: resolve(userHome, ".claude", "projects") },
|
|
132
|
+
{ name: `Gemini CLI (${user})`, path: resolve(userHome, ".gemini", "tmp") },
|
|
133
|
+
{ name: `OpenClaw (${user})`, path: resolve(userHome, ".openclaw", "agents", "main", "sessions") }
|
|
134
|
+
];
|
|
135
|
+
for (const d of otherPaths) {
|
|
136
|
+
if (existsSync(d.path)) {
|
|
137
|
+
console.log(` Found ${d.name}: ${d.path}`);
|
|
138
|
+
found.push(d.path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (process.platform !== "win32") {
|
|
146
|
+
try {
|
|
147
|
+
const { readdirSync: readdirSync2 } = __require("fs");
|
|
148
|
+
for (const baseDir of ["/home", "/Users"]) {
|
|
149
|
+
if (!existsSync(baseDir)) continue;
|
|
150
|
+
for (const user of readdirSync2(baseDir)) {
|
|
151
|
+
const userHome = join(baseDir, user);
|
|
152
|
+
if (userHome === home) continue;
|
|
153
|
+
const otherPaths = [
|
|
154
|
+
{ name: `Claude Code (${user})`, path: resolve(userHome, ".claude", "projects") },
|
|
155
|
+
{ name: `Gemini CLI (${user})`, path: resolve(userHome, ".gemini", "tmp") },
|
|
156
|
+
{ name: `OpenClaw (${user})`, path: resolve(userHome, ".openclaw", "agents", "main", "sessions") }
|
|
157
|
+
];
|
|
158
|
+
for (const d of otherPaths) {
|
|
159
|
+
try {
|
|
160
|
+
if (existsSync(d.path)) {
|
|
161
|
+
console.log(` Found ${d.name}: ${d.path}`);
|
|
162
|
+
found.push(d.path);
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (found.length === 0) {
|
|
173
|
+
console.log(" No AI tool logs found.\n");
|
|
174
|
+
} else {
|
|
175
|
+
console.log(`
|
|
176
|
+
Found ${found.length} log location(s).`);
|
|
177
|
+
console.log(" Default locations are scanned automatically.");
|
|
178
|
+
console.log(" To add a non-default location: npx solongate-audit --add-dir <path>\n");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/audit/collector.ts
|
|
183
|
+
function collectClaude() {
|
|
184
|
+
const sessions = [];
|
|
185
|
+
const claudeDir = resolve2(homedir2(), ".claude", "projects");
|
|
186
|
+
if (!existsSync2(claudeDir)) return sessions;
|
|
187
|
+
for (const projectDir of readdirSync(claudeDir)) {
|
|
188
|
+
const projectPath = join2(claudeDir, projectDir);
|
|
189
|
+
if (!statSync(projectPath).isDirectory()) continue;
|
|
190
|
+
for (const file of readdirSync(projectPath)) {
|
|
191
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
192
|
+
const filePath = join2(projectPath, file);
|
|
193
|
+
const sessionId = file.replace(".jsonl", "");
|
|
194
|
+
try {
|
|
195
|
+
const lines = readFileSync2(filePath, "utf-8").split("\n").filter(Boolean);
|
|
196
|
+
const toolCalls = [];
|
|
197
|
+
const userMessages = [];
|
|
198
|
+
let startTime = "";
|
|
199
|
+
let endTime = "";
|
|
200
|
+
let model = "";
|
|
201
|
+
for (const line of lines) {
|
|
202
|
+
try {
|
|
203
|
+
const entry = JSON.parse(line);
|
|
204
|
+
if (!entry.timestamp) continue;
|
|
205
|
+
if (!startTime) startTime = entry.timestamp;
|
|
206
|
+
endTime = entry.timestamp;
|
|
207
|
+
if (entry.type === "assistant") {
|
|
208
|
+
const content = entry.message?.content;
|
|
209
|
+
if (Array.isArray(content)) {
|
|
210
|
+
for (const block of content) {
|
|
211
|
+
if (block.type === "tool_use") {
|
|
212
|
+
toolCalls.push({
|
|
213
|
+
id: block.id || "",
|
|
214
|
+
toolName: block.name || "",
|
|
215
|
+
arguments: block.input || {},
|
|
216
|
+
timestamp: entry.timestamp,
|
|
217
|
+
source: "claude",
|
|
218
|
+
sessionId
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (entry.message?.model) model = entry.message.model;
|
|
224
|
+
}
|
|
225
|
+
if (entry.type === "user") {
|
|
226
|
+
const content = entry.message?.content;
|
|
227
|
+
if (typeof content === "string" && content.length > 0) {
|
|
228
|
+
userMessages.push({ timestamp: entry.timestamp, text: content.slice(0, 500) });
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(content)) {
|
|
231
|
+
for (const block of content) {
|
|
232
|
+
if (block.type === "tool_result") {
|
|
233
|
+
const tc = toolCalls.find((t) => t.id === block.tool_use_id);
|
|
234
|
+
if (tc) {
|
|
235
|
+
const resultContent = block.content;
|
|
236
|
+
if (typeof resultContent === "string") {
|
|
237
|
+
tc.result = resultContent.slice(0, 2e3);
|
|
238
|
+
} else if (Array.isArray(resultContent)) {
|
|
239
|
+
tc.result = resultContent.map((c) => c.text || "").join("\n").slice(0, 2e3);
|
|
240
|
+
}
|
|
241
|
+
tc.isError = !!block.is_error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const um of userMessages) {
|
|
251
|
+
const umTime = new Date(um.timestamp).getTime();
|
|
252
|
+
const nextIdx = toolCalls.findIndex((tc) => new Date(tc.timestamp).getTime() > umTime);
|
|
253
|
+
if (nextIdx >= 0) um.nextToolCallIndex = nextIdx;
|
|
254
|
+
}
|
|
255
|
+
if (toolCalls.length > 0) {
|
|
256
|
+
sessions.push({ id: sessionId, source: "claude", startTime, endTime, model, toolCalls, userMessages, filePath });
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return sessions;
|
|
263
|
+
}
|
|
264
|
+
function collectGemini() {
|
|
265
|
+
const sessions = [];
|
|
266
|
+
const geminiDir = resolve2(homedir2(), ".gemini", "tmp");
|
|
267
|
+
if (!existsSync2(geminiDir)) return sessions;
|
|
268
|
+
for (const projectDir of readdirSync(geminiDir)) {
|
|
269
|
+
const chatsDir = join2(geminiDir, projectDir, "chats");
|
|
270
|
+
if (!existsSync2(chatsDir) || !statSync(chatsDir).isDirectory()) continue;
|
|
271
|
+
for (const file of readdirSync(chatsDir)) {
|
|
272
|
+
if (!file.startsWith("session-") || !file.endsWith(".json")) continue;
|
|
273
|
+
const filePath = join2(chatsDir, file);
|
|
274
|
+
try {
|
|
275
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
276
|
+
const toolCalls = [];
|
|
277
|
+
const userMessages = [];
|
|
278
|
+
const sessionId = data.sessionId || file.replace(".json", "");
|
|
279
|
+
for (const msg of data.messages || []) {
|
|
280
|
+
if (msg.role === "user" && (msg.text || msg.content)) {
|
|
281
|
+
const text = String(msg.text || msg.content).slice(0, 500);
|
|
282
|
+
if (text.length > 0) {
|
|
283
|
+
userMessages.push({ timestamp: msg.timestamp || data.startTime || "", text });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!msg.toolCalls || !Array.isArray(msg.toolCalls)) continue;
|
|
287
|
+
for (const tc of msg.toolCalls) {
|
|
288
|
+
toolCalls.push({
|
|
289
|
+
id: tc.id || "",
|
|
290
|
+
toolName: tc.name || "",
|
|
291
|
+
arguments: tc.args || tc.arguments || {},
|
|
292
|
+
result: tc.result ? JSON.stringify(tc.result).slice(0, 2e3) : void 0,
|
|
293
|
+
isError: tc.status === "error",
|
|
294
|
+
timestamp: tc.timestamp || msg.timestamp || data.startTime || "",
|
|
295
|
+
source: "gemini",
|
|
296
|
+
sessionId
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const um of userMessages) {
|
|
301
|
+
const umTime = new Date(um.timestamp).getTime();
|
|
302
|
+
const nextIdx = toolCalls.findIndex((tc) => new Date(tc.timestamp).getTime() > umTime);
|
|
303
|
+
if (nextIdx >= 0) um.nextToolCallIndex = nextIdx;
|
|
304
|
+
}
|
|
305
|
+
if (toolCalls.length > 0) {
|
|
306
|
+
sessions.push({
|
|
307
|
+
id: sessionId,
|
|
308
|
+
source: "gemini",
|
|
309
|
+
startTime: data.startTime || "",
|
|
310
|
+
endTime: data.lastUpdated || "",
|
|
311
|
+
model: data.messages?.[0]?.model || "",
|
|
312
|
+
toolCalls,
|
|
313
|
+
userMessages,
|
|
314
|
+
filePath
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return sessions;
|
|
322
|
+
}
|
|
323
|
+
function collectOpenClaw() {
|
|
324
|
+
const sessions = [];
|
|
325
|
+
const oclawDir = resolve2(homedir2(), ".openclaw", "agents", "main", "sessions");
|
|
326
|
+
if (!existsSync2(oclawDir)) return sessions;
|
|
327
|
+
for (const file of readdirSync(oclawDir)) {
|
|
328
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
329
|
+
const filePath = join2(oclawDir, file);
|
|
330
|
+
const sessionId = file.replace(".jsonl", "");
|
|
331
|
+
try {
|
|
332
|
+
const lines = readFileSync2(filePath, "utf-8").split("\n").filter(Boolean);
|
|
333
|
+
const toolCalls = [];
|
|
334
|
+
const userMessages = [];
|
|
335
|
+
let startTime = "";
|
|
336
|
+
let endTime = "";
|
|
337
|
+
let model = "";
|
|
338
|
+
for (const line of lines) {
|
|
339
|
+
try {
|
|
340
|
+
const entry = JSON.parse(line);
|
|
341
|
+
if (!entry.timestamp) continue;
|
|
342
|
+
if (!startTime) startTime = entry.timestamp;
|
|
343
|
+
endTime = entry.timestamp;
|
|
344
|
+
if (entry.type === "model_change") {
|
|
345
|
+
model = entry.modelId || "";
|
|
346
|
+
}
|
|
347
|
+
if (entry.type === "message" && entry.message?.role === "user") {
|
|
348
|
+
const content = entry.message.content;
|
|
349
|
+
if (typeof content === "string" && content.length > 0) {
|
|
350
|
+
userMessages.push({ timestamp: entry.timestamp, text: content.slice(0, 500) });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
354
|
+
const content = entry.message.content;
|
|
355
|
+
if (Array.isArray(content)) {
|
|
356
|
+
for (const block of content) {
|
|
357
|
+
if (block.type === "toolCall" || block.type === "tool_use") {
|
|
358
|
+
toolCalls.push({
|
|
359
|
+
id: block.id || "",
|
|
360
|
+
toolName: block.name || "",
|
|
361
|
+
arguments: block.arguments || block.input || {},
|
|
362
|
+
timestamp: entry.timestamp,
|
|
363
|
+
source: "openclaw",
|
|
364
|
+
sessionId
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (entry.type === "message" && entry.message?.role === "toolResult") {
|
|
371
|
+
const tc = toolCalls.find((t) => t.id === entry.message.toolCallId);
|
|
372
|
+
if (tc) {
|
|
373
|
+
const content = entry.message.content;
|
|
374
|
+
if (Array.isArray(content)) {
|
|
375
|
+
tc.result = content.map((c) => c.text || "").join("\n").slice(0, 2e3);
|
|
376
|
+
}
|
|
377
|
+
tc.isError = !!entry.message.isError;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const um of userMessages) {
|
|
384
|
+
const umTime = new Date(um.timestamp).getTime();
|
|
385
|
+
const nextIdx = toolCalls.findIndex((tc) => new Date(tc.timestamp).getTime() > umTime);
|
|
386
|
+
if (nextIdx >= 0) um.nextToolCallIndex = nextIdx;
|
|
387
|
+
}
|
|
388
|
+
if (toolCalls.length > 0) {
|
|
389
|
+
sessions.push({ id: sessionId, source: "openclaw", startTime, endTime, model, toolCalls, userMessages, filePath });
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return sessions;
|
|
395
|
+
}
|
|
396
|
+
function collectCustomDirs() {
|
|
397
|
+
const sessions = [];
|
|
398
|
+
const config = loadConfig();
|
|
399
|
+
for (const dir of config.customDirs) {
|
|
400
|
+
if (!existsSync2(dir)) continue;
|
|
401
|
+
try {
|
|
402
|
+
for (const file of readdirSync(dir)) {
|
|
403
|
+
const filePath = join2(dir, file);
|
|
404
|
+
try {
|
|
405
|
+
if (!statSync(filePath).isFile()) continue;
|
|
406
|
+
} catch {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (file.endsWith(".jsonl")) {
|
|
410
|
+
try {
|
|
411
|
+
const lines = readFileSync2(filePath, "utf-8").split("\n").filter(Boolean);
|
|
412
|
+
const toolCalls = [];
|
|
413
|
+
let startTime = "";
|
|
414
|
+
let endTime = "";
|
|
415
|
+
let model = "";
|
|
416
|
+
const sessionId = file.replace(".jsonl", "");
|
|
417
|
+
for (const line of lines) {
|
|
418
|
+
try {
|
|
419
|
+
const entry = JSON.parse(line);
|
|
420
|
+
if (!entry.timestamp) continue;
|
|
421
|
+
if (!startTime) startTime = entry.timestamp;
|
|
422
|
+
endTime = entry.timestamp;
|
|
423
|
+
if (entry.type === "model_change") model = entry.modelId || "";
|
|
424
|
+
if (entry.type === "assistant") {
|
|
425
|
+
const content = entry.message?.content;
|
|
426
|
+
if (Array.isArray(content)) {
|
|
427
|
+
for (const block of content) {
|
|
428
|
+
if (block.type === "tool_use") {
|
|
429
|
+
toolCalls.push({ id: block.id || "", toolName: block.name || "", arguments: block.input || {}, timestamp: entry.timestamp, source: "claude", sessionId });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (entry.message?.model) model = entry.message.model;
|
|
434
|
+
}
|
|
435
|
+
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
436
|
+
const content = entry.message.content;
|
|
437
|
+
if (Array.isArray(content)) {
|
|
438
|
+
for (const block of content) {
|
|
439
|
+
if (block.type === "toolCall" || block.type === "tool_use") {
|
|
440
|
+
toolCalls.push({ id: block.id || "", toolName: block.name || "", arguments: block.arguments || block.input || {}, timestamp: entry.timestamp, source: "openclaw", sessionId });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (entry.type === "user") {
|
|
446
|
+
const content = entry.message?.content;
|
|
447
|
+
if (Array.isArray(content)) {
|
|
448
|
+
for (const block of content) {
|
|
449
|
+
if (block.type === "tool_result") {
|
|
450
|
+
const tc = toolCalls.find((t) => t.id === block.tool_use_id);
|
|
451
|
+
if (tc) {
|
|
452
|
+
const rc = block.content;
|
|
453
|
+
tc.result = typeof rc === "string" ? rc.slice(0, 2e3) : Array.isArray(rc) ? rc.map((c) => c.text || "").join("\n").slice(0, 2e3) : void 0;
|
|
454
|
+
tc.isError = !!block.is_error;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (entry.type === "message" && entry.message?.role === "toolResult") {
|
|
461
|
+
const tc = toolCalls.find((t) => t.id === entry.message.toolCallId);
|
|
462
|
+
if (tc) {
|
|
463
|
+
const content = entry.message.content;
|
|
464
|
+
if (Array.isArray(content)) {
|
|
465
|
+
tc.result = content.map((c) => c.text || "").join("\n").slice(0, 2e3);
|
|
466
|
+
}
|
|
467
|
+
tc.isError = !!entry.message.isError;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (toolCalls.length > 0) {
|
|
474
|
+
sessions.push({ id: sessionId, source: toolCalls[0].source, startTime, endTime, model, toolCalls, filePath });
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (file.endsWith(".json")) {
|
|
480
|
+
try {
|
|
481
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
482
|
+
if (!data.messages || !Array.isArray(data.messages)) continue;
|
|
483
|
+
const toolCalls = [];
|
|
484
|
+
const sessionId = data.sessionId || file.replace(".json", "");
|
|
485
|
+
for (const msg of data.messages) {
|
|
486
|
+
if (!msg.toolCalls || !Array.isArray(msg.toolCalls)) continue;
|
|
487
|
+
for (const tc of msg.toolCalls) {
|
|
488
|
+
toolCalls.push({ id: tc.id || "", toolName: tc.name || "", arguments: tc.args || tc.arguments || {}, result: tc.result ? JSON.stringify(tc.result).slice(0, 2e3) : void 0, isError: tc.status === "error", timestamp: tc.timestamp || msg.timestamp || data.startTime || "", source: "gemini", sessionId });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (toolCalls.length > 0) {
|
|
492
|
+
sessions.push({ id: sessionId, source: "gemini", startTime: data.startTime || "", endTime: data.lastUpdated || "", model: data.messages?.[0]?.model || "", toolCalls, filePath });
|
|
493
|
+
}
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return sessions;
|
|
502
|
+
}
|
|
503
|
+
function collectLogs() {
|
|
504
|
+
const claudeSessions = collectClaude();
|
|
505
|
+
const geminiSessions = collectGemini();
|
|
506
|
+
const openclawSessions = collectOpenClaw();
|
|
507
|
+
const customSessions = collectCustomDirs();
|
|
508
|
+
const sessions = [...claudeSessions, ...geminiSessions, ...openclawSessions, ...customSessions].sort((a, b) => (a.startTime || "").localeCompare(b.startTime || ""));
|
|
509
|
+
const sources = [];
|
|
510
|
+
if (claudeSessions.length > 0) sources.push("Claude Code");
|
|
511
|
+
if (geminiSessions.length > 0) sources.push("Gemini CLI");
|
|
512
|
+
if (openclawSessions.length > 0) sources.push("OpenClaw");
|
|
513
|
+
const allCalls = sessions.flatMap((s) => s.toolCalls);
|
|
514
|
+
const timestamps = allCalls.map((t) => t.timestamp).filter(Boolean).sort();
|
|
515
|
+
return {
|
|
516
|
+
sessions,
|
|
517
|
+
totalToolCalls: allCalls.length,
|
|
518
|
+
sources,
|
|
519
|
+
timeRange: timestamps.length > 0 ? { from: timestamps[0], to: timestamps[timestamps.length - 1] } : null
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/audit/checks/chain-analysis.ts
|
|
524
|
+
function isFileRead(tc) {
|
|
525
|
+
const t = tc.toolName.toLowerCase();
|
|
526
|
+
return t.includes("read") || t.includes("cat") || t.includes("grep") || t.includes("glob");
|
|
527
|
+
}
|
|
528
|
+
function isFileWrite(tc) {
|
|
529
|
+
const t = tc.toolName.toLowerCase();
|
|
530
|
+
return t.includes("write") || t.includes("edit");
|
|
531
|
+
}
|
|
532
|
+
function isShellExec(tc) {
|
|
533
|
+
const t = tc.toolName.toLowerCase();
|
|
534
|
+
return t.includes("bash") || t.includes("shell") || t.includes("exec") || t.includes("terminal");
|
|
535
|
+
}
|
|
536
|
+
function isNetworkCall(tc) {
|
|
537
|
+
const t = tc.toolName.toLowerCase();
|
|
538
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
539
|
+
return t.includes("web") || t.includes("fetch") || t.includes("navigate") || isShellExec(tc) && (argStr.includes("curl ") || argStr.includes("wget ") || argStr.includes("nc ") || argStr.includes("scp "));
|
|
540
|
+
}
|
|
541
|
+
function isPrivilegeEscalation(tc) {
|
|
542
|
+
if (!isShellExec(tc)) return false;
|
|
543
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
544
|
+
return /\bsudo\b/.test(argStr) || /\bchmod\b/.test(argStr) || /\bchown\b/.test(argStr) || /\brunas\b/.test(argStr);
|
|
545
|
+
}
|
|
546
|
+
function readsSensitiveFile(tc) {
|
|
547
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
548
|
+
return /\.env(?:\b|\.production|\.local|\.staging)/.test(argStr) || argStr.includes("credentials") || argStr.includes("id_rsa") || argStr.includes("id_ed25519") || argStr.includes(".pem") || argStr.includes("/etc/shadow") || argStr.includes("service-account");
|
|
549
|
+
}
|
|
550
|
+
function extractSummary(tc) {
|
|
551
|
+
const args2 = tc.arguments;
|
|
552
|
+
return String(args2.command || args2.file_path || args2.path || args2.url || JSON.stringify(args2).slice(0, 60));
|
|
553
|
+
}
|
|
554
|
+
var CHAIN_PATTERNS = [
|
|
555
|
+
{
|
|
556
|
+
name: "credential-exfiltration",
|
|
557
|
+
severity: "high",
|
|
558
|
+
description: "Credential file read followed by network call",
|
|
559
|
+
steps: [readsSensitiveFile, isNetworkCall],
|
|
560
|
+
maxGap: 10
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "cross-contamination",
|
|
564
|
+
severity: "medium",
|
|
565
|
+
description: "File read followed by file write containing read content",
|
|
566
|
+
steps: [isFileRead, isFileWrite],
|
|
567
|
+
maxGap: 5,
|
|
568
|
+
requireContentOverlap: true
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "privilege-escalation-sequence",
|
|
572
|
+
severity: "high",
|
|
573
|
+
description: "Normal read followed by privilege escalation then execution",
|
|
574
|
+
steps: [isFileRead, isPrivilegeEscalation, isShellExec],
|
|
575
|
+
maxGap: 8
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "read-exec-chain",
|
|
579
|
+
severity: "medium",
|
|
580
|
+
description: "File read followed by shell exec using read content",
|
|
581
|
+
steps: [
|
|
582
|
+
(tc) => isFileRead(tc) && !readsSensitiveFile(tc),
|
|
583
|
+
(tc) => isShellExec(tc) && !isPrivilegeEscalation(tc)
|
|
584
|
+
],
|
|
585
|
+
maxGap: 3,
|
|
586
|
+
requireContentOverlap: true
|
|
587
|
+
}
|
|
588
|
+
];
|
|
589
|
+
function hasContentOverlap(source, sink) {
|
|
590
|
+
const sourceResult = source.result || "";
|
|
591
|
+
if (sourceResult.length < 20) return false;
|
|
592
|
+
const sinkArgs = JSON.stringify(sink.arguments);
|
|
593
|
+
for (let i = 0; i < Math.min(sourceResult.length - 30, 500); i += 15) {
|
|
594
|
+
const chunk = sourceResult.slice(i, i + 30).trim();
|
|
595
|
+
if (chunk.length < 15) continue;
|
|
596
|
+
if (sinkArgs.includes(chunk)) return true;
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
function findRetryStorms(session) {
|
|
601
|
+
const matches = [];
|
|
602
|
+
const calls = session.toolCalls;
|
|
603
|
+
for (let i = 0; i < calls.length - 3; i++) {
|
|
604
|
+
const window = calls.slice(i, i + 4);
|
|
605
|
+
if (window.every((c) => c.toolName === window[0].toolName && c.isError)) {
|
|
606
|
+
matches.push({
|
|
607
|
+
chainName: "retry-storm",
|
|
608
|
+
sessionId: session.id,
|
|
609
|
+
steps: window.map((c, j) => ({
|
|
610
|
+
index: i + j,
|
|
611
|
+
toolName: c.toolName,
|
|
612
|
+
summary: `error: ${(c.result || "").slice(0, 50)}`
|
|
613
|
+
})),
|
|
614
|
+
severity: "low",
|
|
615
|
+
description: `${window[0].toolName} called ${window.length}+ times with consecutive errors`
|
|
616
|
+
});
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return matches;
|
|
621
|
+
}
|
|
622
|
+
function findChainMatches(session) {
|
|
623
|
+
const matches = [];
|
|
624
|
+
const calls = session.toolCalls;
|
|
625
|
+
matches.push(...findRetryStorms(session));
|
|
626
|
+
for (const pattern of CHAIN_PATTERNS) {
|
|
627
|
+
for (let startIdx = 0; startIdx < calls.length; startIdx++) {
|
|
628
|
+
if (!pattern.steps[0](calls[startIdx])) continue;
|
|
629
|
+
const stepMatches = [
|
|
630
|
+
{ index: startIdx, toolName: calls[startIdx].toolName, summary: extractSummary(calls[startIdx]) }
|
|
631
|
+
];
|
|
632
|
+
let currentIdx = startIdx;
|
|
633
|
+
let matched = true;
|
|
634
|
+
for (let stepNum = 1; stepNum < pattern.steps.length; stepNum++) {
|
|
635
|
+
let found = false;
|
|
636
|
+
for (let j = currentIdx + 1; j <= Math.min(currentIdx + pattern.maxGap, calls.length - 1); j++) {
|
|
637
|
+
if (pattern.steps[stepNum](calls[j])) {
|
|
638
|
+
stepMatches.push({ index: j, toolName: calls[j].toolName, summary: extractSummary(calls[j]) });
|
|
639
|
+
currentIdx = j;
|
|
640
|
+
found = true;
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (!found) {
|
|
645
|
+
matched = false;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (!matched) continue;
|
|
650
|
+
if (pattern.requireContentOverlap) {
|
|
651
|
+
if (!hasContentOverlap(calls[stepMatches[0].index], calls[stepMatches[stepMatches.length - 1].index])) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
matches.push({
|
|
656
|
+
chainName: pattern.name,
|
|
657
|
+
sessionId: session.id,
|
|
658
|
+
steps: stepMatches,
|
|
659
|
+
severity: pattern.severity,
|
|
660
|
+
description: pattern.description
|
|
661
|
+
});
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return matches;
|
|
666
|
+
}
|
|
667
|
+
function analyzeChains(sessions) {
|
|
668
|
+
const allMatches = [];
|
|
669
|
+
for (const session of sessions) {
|
|
670
|
+
allMatches.push(...findChainMatches(session));
|
|
671
|
+
}
|
|
672
|
+
return allMatches;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/audit/checks/data-flow.ts
|
|
676
|
+
var SENSITIVE_PATTERNS = [
|
|
677
|
+
{ pattern: /sk-[a-zA-Z0-9]{20,}/, type: "openai_api_key" },
|
|
678
|
+
{ pattern: /sk_live_[a-zA-Z0-9]+/, type: "stripe_key" },
|
|
679
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9._\-]{20,}/, type: "bearer_token" },
|
|
680
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, type: "github_pat" },
|
|
681
|
+
{ pattern: /glpat-[a-zA-Z0-9\-]{20,}/, type: "gitlab_pat" },
|
|
682
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{16,})['"]?/i, type: "api_key" },
|
|
683
|
+
{ pattern: /password\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/i, type: "password" },
|
|
684
|
+
{ pattern: /AWS[_A-Z]*KEY[_A-Z]*\s*[:=]\s*['"]?([A-Z0-9]{16,})['"]?/i, type: "aws_key" },
|
|
685
|
+
{ pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/, type: "private_key" },
|
|
686
|
+
{ pattern: /DATABASE_URL\s*[:=]\s*['"]?([^\s'"]+)['"]?/i, type: "database_url" }
|
|
687
|
+
];
|
|
688
|
+
var SENSITIVE_FILE_PATTERNS = [
|
|
689
|
+
/\.env(?:\b|\.production|\.local|\.staging)/,
|
|
690
|
+
/credentials/i,
|
|
691
|
+
/secrets?\b/i,
|
|
692
|
+
/id_rsa/,
|
|
693
|
+
/id_ed25519/,
|
|
694
|
+
/\.pem$/,
|
|
695
|
+
/service.account/i,
|
|
696
|
+
/\.npmrc/
|
|
697
|
+
];
|
|
698
|
+
function isSensitiveFileAccess(tc) {
|
|
699
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
700
|
+
return SENSITIVE_FILE_PATTERNS.some((p) => p.test(argStr));
|
|
701
|
+
}
|
|
702
|
+
function isNetworkOrExecSink(tc) {
|
|
703
|
+
const t = tc.toolName.toLowerCase();
|
|
704
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
705
|
+
return t.includes("web") || t.includes("fetch") || t.includes("navigate") || (t.includes("bash") || t.includes("shell") || t.includes("exec")) && (argStr.includes("curl") || argStr.includes("wget") || argStr.includes("nc ") || argStr.includes("scp ") || argStr.includes("http"));
|
|
706
|
+
}
|
|
707
|
+
function analyzeDataFlow(sessions) {
|
|
708
|
+
const leaks = [];
|
|
709
|
+
for (const session of sessions) {
|
|
710
|
+
const calls = session.toolCalls;
|
|
711
|
+
for (let i = 0; i < calls.length; i++) {
|
|
712
|
+
const source = calls[i];
|
|
713
|
+
const sourceResult = source.result || "";
|
|
714
|
+
if (sourceResult.length < 10) continue;
|
|
715
|
+
const foundTokens = [];
|
|
716
|
+
for (const { pattern, type } of SENSITIVE_PATTERNS) {
|
|
717
|
+
const match = sourceResult.match(pattern);
|
|
718
|
+
if (match) {
|
|
719
|
+
foundTokens.push({ type, value: (match[1] || match[0]).slice(0, 30) });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const isSensitiveRead = isSensitiveFileAccess(source);
|
|
723
|
+
if (foundTokens.length === 0 && !isSensitiveRead) continue;
|
|
724
|
+
for (let j = i + 1; j < Math.min(i + 20, calls.length); j++) {
|
|
725
|
+
const sink = calls[j];
|
|
726
|
+
if (!isNetworkOrExecSink(sink)) continue;
|
|
727
|
+
const sinkArgs = JSON.stringify(sink.arguments);
|
|
728
|
+
for (const token of foundTokens) {
|
|
729
|
+
if (sinkArgs.includes(token.value.slice(0, 15))) {
|
|
730
|
+
leaks.push({
|
|
731
|
+
sessionId: session.id,
|
|
732
|
+
sourceIndex: i,
|
|
733
|
+
sinkIndex: j,
|
|
734
|
+
sourceToolName: source.toolName,
|
|
735
|
+
sinkToolName: sink.toolName,
|
|
736
|
+
dataType: token.type,
|
|
737
|
+
pattern: token.value.slice(0, 20) + "..."
|
|
738
|
+
});
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (isSensitiveRead && sourceResult.length > 20) {
|
|
743
|
+
for (let k = 0; k < Math.min(sourceResult.length - 20, 300); k += 20) {
|
|
744
|
+
const chunk = sourceResult.slice(k, k + 20).trim();
|
|
745
|
+
if (chunk.length < 10) continue;
|
|
746
|
+
if (sinkArgs.includes(chunk)) {
|
|
747
|
+
leaks.push({
|
|
748
|
+
sessionId: session.id,
|
|
749
|
+
sourceIndex: i,
|
|
750
|
+
sinkIndex: j,
|
|
751
|
+
sourceToolName: source.toolName,
|
|
752
|
+
sinkToolName: sink.toolName,
|
|
753
|
+
dataType: "file_content",
|
|
754
|
+
pattern: chunk.slice(0, 20) + "..."
|
|
755
|
+
});
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return leaks;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/audit/checks/baseline.ts
|
|
767
|
+
function getPrivilegeLevel(toolName, args2) {
|
|
768
|
+
const t = toolName.toLowerCase();
|
|
769
|
+
const argStr = JSON.stringify(args2).toLowerCase();
|
|
770
|
+
if (t.includes("web") || t.includes("fetch") || t.includes("navigate")) return 4;
|
|
771
|
+
if ((t.includes("bash") || t.includes("shell") || t.includes("exec")) && (argStr.includes("curl") || argStr.includes("wget") || argStr.includes("nc "))) return 4;
|
|
772
|
+
if (t.includes("bash") || t.includes("shell") || t.includes("exec") || t.includes("terminal")) return 3;
|
|
773
|
+
if (t.includes("write") || t.includes("edit") || t.includes("delete") || t.includes("notebook")) return 2;
|
|
774
|
+
return 1;
|
|
775
|
+
}
|
|
776
|
+
function getToolCategory(toolName) {
|
|
777
|
+
const t = toolName.toLowerCase();
|
|
778
|
+
if (t.includes("read") || t.includes("cat") || t.includes("grep") || t.includes("glob") || t.includes("search")) return "read";
|
|
779
|
+
if (t.includes("write") || t.includes("edit")) return "write";
|
|
780
|
+
if (t.includes("bash") || t.includes("shell") || t.includes("exec") || t.includes("terminal")) return "exec";
|
|
781
|
+
if (t.includes("web") || t.includes("fetch") || t.includes("navigate") || t.includes("browser")) return "network";
|
|
782
|
+
if (t.includes("task") || t.includes("todo") || t.includes("agent")) return "orchestration";
|
|
783
|
+
return "other";
|
|
784
|
+
}
|
|
785
|
+
function computeBaseline(sessions, source) {
|
|
786
|
+
const filtered = source === "all" ? sessions : sessions.filter((s) => s.source === source);
|
|
787
|
+
const callCounts = filtered.map((s) => s.toolCalls.length);
|
|
788
|
+
const avgToolCalls = callCounts.length > 0 ? callCounts.reduce((a, b) => a + b, 0) / callCounts.length : 0;
|
|
789
|
+
const stddevToolCalls = callCounts.length > 1 ? Math.sqrt(callCounts.reduce((sum, c) => sum + (c - avgToolCalls) ** 2, 0) / (callCounts.length - 1)) : 0;
|
|
790
|
+
const durations = filtered.filter((s) => s.startTime && s.endTime).map((s) => new Date(s.endTime).getTime() - new Date(s.startTime).getTime()).filter((d) => !isNaN(d) && d > 0);
|
|
791
|
+
const avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;
|
|
792
|
+
const stddevDurationMs = durations.length > 1 ? Math.sqrt(durations.reduce((sum, d) => sum + (d - avgDurationMs) ** 2, 0) / (durations.length - 1)) : 0;
|
|
793
|
+
const totalCalls = filtered.reduce((sum, s) => sum + s.toolCalls.length, 0);
|
|
794
|
+
const categoryCount = {};
|
|
795
|
+
for (const s of filtered) {
|
|
796
|
+
for (const tc of s.toolCalls) {
|
|
797
|
+
const cat = getToolCategory(tc.toolName);
|
|
798
|
+
categoryCount[cat] = (categoryCount[cat] || 0) + 1;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const toolTypeDistribution = {};
|
|
802
|
+
for (const [cat, count] of Object.entries(categoryCount)) {
|
|
803
|
+
toolTypeDistribution[cat] = totalCalls > 0 ? count / totalCalls * 100 : 0;
|
|
804
|
+
}
|
|
805
|
+
return { source, avgToolCalls, stddevToolCalls, toolTypeDistribution, avgDurationMs, stddevDurationMs };
|
|
806
|
+
}
|
|
807
|
+
function detectAnomalies(sessions, baselines) {
|
|
808
|
+
const anomalies = [];
|
|
809
|
+
const allBaseline = baselines.find((b) => b.source === "all");
|
|
810
|
+
if (!allBaseline || sessions.length < 3) return anomalies;
|
|
811
|
+
for (const session of sessions) {
|
|
812
|
+
const baseline = baselines.find((b) => b.source === session.source) || allBaseline;
|
|
813
|
+
const deviations = [];
|
|
814
|
+
if (baseline.stddevToolCalls > 0) {
|
|
815
|
+
const zScore = (session.toolCalls.length - baseline.avgToolCalls) / baseline.stddevToolCalls;
|
|
816
|
+
if (zScore > 2) {
|
|
817
|
+
deviations.push(`tool calls ${zScore.toFixed(1)}x stddev above mean (${session.toolCalls.length} vs avg ${Math.round(baseline.avgToolCalls)})`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (session.startTime && session.endTime && baseline.stddevDurationMs > 0) {
|
|
821
|
+
const duration = new Date(session.endTime).getTime() - new Date(session.startTime).getTime();
|
|
822
|
+
if (!isNaN(duration) && duration > 0) {
|
|
823
|
+
const zScore = (duration - baseline.avgDurationMs) / baseline.stddevDurationMs;
|
|
824
|
+
if (zScore > 2) {
|
|
825
|
+
deviations.push(`duration ${zScore.toFixed(1)}x stddev above mean`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const sessionCategories = {};
|
|
830
|
+
for (const tc of session.toolCalls) {
|
|
831
|
+
const cat = getToolCategory(tc.toolName);
|
|
832
|
+
sessionCategories[cat] = (sessionCategories[cat] || 0) + 1;
|
|
833
|
+
}
|
|
834
|
+
const sessionTotal = session.toolCalls.length || 1;
|
|
835
|
+
for (const [cat, count] of Object.entries(sessionCategories)) {
|
|
836
|
+
const sessionPct = count / sessionTotal * 100;
|
|
837
|
+
const baselinePct = baseline.toolTypeDistribution[cat] || 0;
|
|
838
|
+
if (sessionPct > baselinePct * 3 && sessionPct > 10 && baselinePct > 0) {
|
|
839
|
+
deviations.push(`${cat}: ${sessionPct.toFixed(0)}% vs baseline ${baselinePct.toFixed(0)}%`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (deviations.length > 0) {
|
|
843
|
+
anomalies.push({
|
|
844
|
+
sessionId: session.id,
|
|
845
|
+
source: session.source,
|
|
846
|
+
deviations,
|
|
847
|
+
severity: deviations.length >= 3 ? "high" : deviations.length >= 2 ? "medium" : "low"
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return anomalies;
|
|
852
|
+
}
|
|
853
|
+
function detectPermissionDrift(sessions) {
|
|
854
|
+
const drifts = [];
|
|
855
|
+
for (const session of sessions) {
|
|
856
|
+
const calls = session.toolCalls;
|
|
857
|
+
if (calls.length < 8) continue;
|
|
858
|
+
const mid = Math.floor(calls.length / 2);
|
|
859
|
+
const firstHalf = calls.slice(0, mid);
|
|
860
|
+
const secondHalf = calls.slice(mid);
|
|
861
|
+
const earlyLevels = firstHalf.map((tc) => getPrivilegeLevel(tc.toolName, tc.arguments));
|
|
862
|
+
const lateLevels = secondHalf.map((tc) => getPrivilegeLevel(tc.toolName, tc.arguments));
|
|
863
|
+
const earlyAvg = earlyLevels.reduce((a, b) => a + b, 0) / earlyLevels.length;
|
|
864
|
+
const lateAvg = lateLevels.reduce((a, b) => a + b, 0) / lateLevels.length;
|
|
865
|
+
const driftRatio = earlyAvg > 0 ? lateAvg / earlyAvg : lateAvg;
|
|
866
|
+
const earlyTools = new Set(firstHalf.map((tc) => getToolCategory(tc.toolName)));
|
|
867
|
+
const lateTools = new Set(secondHalf.map((tc) => getToolCategory(tc.toolName)));
|
|
868
|
+
const newToolTypes = [...lateTools].filter((t) => !earlyTools.has(t));
|
|
869
|
+
if (driftRatio > 1.5 || newToolTypes.includes("exec") || newToolTypes.includes("network")) {
|
|
870
|
+
drifts.push({
|
|
871
|
+
sessionId: session.id,
|
|
872
|
+
earlyPrivilegeLevel: earlyAvg,
|
|
873
|
+
latePrivilegeLevel: lateAvg,
|
|
874
|
+
driftRatio,
|
|
875
|
+
newToolTypes
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return drifts;
|
|
880
|
+
}
|
|
881
|
+
var CRITICAL_ACTIONS = [
|
|
882
|
+
{ pattern: /\b(git\s+push|npm\s+publish|docker\s+push|kubectl\s+apply|terraform\s+apply)\b/i, action: "deploy" },
|
|
883
|
+
{ pattern: /rm\s+-rf\s+[\/~$.*]|del\s+\/[sf]|drop\s+(table|database)/i, action: "delete" },
|
|
884
|
+
{ pattern: /chmod\s+[0-7]{3,4}|chown\b|sudo\b/i, action: "privilege" }
|
|
885
|
+
];
|
|
886
|
+
function actionMatchesUserRequest(action, userText) {
|
|
887
|
+
const lower = userText.toLowerCase();
|
|
888
|
+
switch (action) {
|
|
889
|
+
case "deploy":
|
|
890
|
+
return /\b(deploy|push|publish|release|ship|yayınla|gönder)\b/.test(lower);
|
|
891
|
+
case "delete":
|
|
892
|
+
return /\b(delete|remove|clean|drop|purge|sil|kaldır|temizle)\b/.test(lower);
|
|
893
|
+
case "privilege":
|
|
894
|
+
return /\b(chmod|chown|sudo|permission|root|admin|yetki)\b/.test(lower);
|
|
895
|
+
default:
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function findUnsolicitedActions(sessions) {
|
|
900
|
+
const unsolicited = [];
|
|
901
|
+
for (const session of sessions) {
|
|
902
|
+
const userMsgs = session.userMessages || [];
|
|
903
|
+
if (userMsgs.length === 0) continue;
|
|
904
|
+
for (let i = 0; i < session.toolCalls.length; i++) {
|
|
905
|
+
const tc = session.toolCalls[i];
|
|
906
|
+
const t = tc.toolName.toLowerCase();
|
|
907
|
+
if (!(t.includes("bash") || t.includes("shell") || t.includes("exec"))) continue;
|
|
908
|
+
const argStr = JSON.stringify(tc.arguments);
|
|
909
|
+
for (const { pattern, action } of CRITICAL_ACTIONS) {
|
|
910
|
+
if (!pattern.test(argStr)) continue;
|
|
911
|
+
const tcTime = new Date(tc.timestamp).getTime();
|
|
912
|
+
const precedingMsgs = userMsgs.filter((um) => new Date(um.timestamp).getTime() < tcTime);
|
|
913
|
+
const lastMsg = precedingMsgs.length > 0 ? precedingMsgs[precedingMsgs.length - 1] : void 0;
|
|
914
|
+
const userRequestedIt = lastMsg && actionMatchesUserRequest(action, lastMsg.text);
|
|
915
|
+
if (!userRequestedIt) {
|
|
916
|
+
unsolicited.push({
|
|
917
|
+
sessionId: session.id,
|
|
918
|
+
toolCallIndex: i,
|
|
919
|
+
toolName: tc.toolName,
|
|
920
|
+
action,
|
|
921
|
+
lastUserMessageBefore: lastMsg?.text.slice(0, 100),
|
|
922
|
+
timeSinceLastUserMessage: lastMsg ? tcTime - new Date(lastMsg.timestamp).getTime() : void 0
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return unsolicited;
|
|
930
|
+
}
|
|
931
|
+
function analyzeBaseline(sessions) {
|
|
932
|
+
const sources = [...new Set(sessions.map((s) => s.source))];
|
|
933
|
+
const baselines = [
|
|
934
|
+
computeBaseline(sessions, "all"),
|
|
935
|
+
...sources.map((s) => computeBaseline(sessions, s))
|
|
936
|
+
];
|
|
937
|
+
const anomalies = detectAnomalies(sessions, baselines);
|
|
938
|
+
const permissionDrifts = detectPermissionDrift(sessions);
|
|
939
|
+
return { baselines, anomalies, permissionDrifts };
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/audit/checks/asi01-goal-hijacking.ts
|
|
943
|
+
var INJECTION_PATTERNS = [
|
|
944
|
+
/ignore\s+(previous|above|all)\s+(instructions|rules)/i,
|
|
945
|
+
/you\s+are\s+now\s+(a|an|the)/i,
|
|
946
|
+
/\<\/?system\s*>/i,
|
|
947
|
+
/\[\s*INST\s*\]/i,
|
|
948
|
+
/act\s+as\s+(a|an|if)\s+you/i,
|
|
949
|
+
/forget\s+(everything|all|your)\s+(instructions|rules|context)/i,
|
|
950
|
+
/override\s+(your|the|all)\s+(instructions|rules|safety)/i,
|
|
951
|
+
/new\s+instructions?\s*:/i,
|
|
952
|
+
/do\s+not\s+follow\s+(any|your|the)\s+(previous|original)/i,
|
|
953
|
+
/disregard\s+(all|any|your)\s+(previous|prior|original)/i
|
|
954
|
+
];
|
|
955
|
+
function checkGoalHijacking(data, deep) {
|
|
956
|
+
const code = "ASI01";
|
|
957
|
+
const title = "Goal Hijacking";
|
|
958
|
+
const evidence = [];
|
|
959
|
+
let injectionAttempts = 0;
|
|
960
|
+
const examples = [];
|
|
961
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
962
|
+
const argStr = JSON.stringify(tc.arguments);
|
|
963
|
+
const resultStr = tc.result || "";
|
|
964
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
965
|
+
if (pattern.test(argStr)) {
|
|
966
|
+
injectionAttempts++;
|
|
967
|
+
if (examples.length < 3) {
|
|
968
|
+
examples.push(`${tc.toolName}: arg matches ${pattern.source.slice(0, 30)}`);
|
|
969
|
+
}
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
974
|
+
if (pattern.test(resultStr)) {
|
|
975
|
+
injectionAttempts++;
|
|
976
|
+
if (examples.length < 3) {
|
|
977
|
+
examples.push(`${tc.toolName}: result contains injection pattern`);
|
|
978
|
+
}
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
evidence.push({ icon: "info", text: `Scanned ${data.totalToolCalls} tool calls for prompt injection patterns` });
|
|
984
|
+
if (injectionAttempts === 0) {
|
|
985
|
+
evidence.push({ icon: "found", text: "No prompt injection patterns detected in tool calls or results" });
|
|
986
|
+
} else {
|
|
987
|
+
evidence.push({ icon: "warn", text: `${injectionAttempts} potential prompt injection pattern(s) detected` });
|
|
988
|
+
for (const ex of examples) {
|
|
989
|
+
evidence.push({ icon: "warn", text: ` ${ex}` });
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
let encodedPayloads = 0;
|
|
993
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
994
|
+
const r = tc.result || "";
|
|
995
|
+
if (/[A-Za-z0-9+/]{50,}={0,2}/.test(r) && /base64|decode|eval/i.test(r)) {
|
|
996
|
+
encodedPayloads++;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (encodedPayloads > 0) {
|
|
1000
|
+
evidence.push({ icon: "warn", text: `${encodedPayloads} tool result(s) contain encoded payloads (potential obfuscated injection)` });
|
|
1001
|
+
}
|
|
1002
|
+
if (deep) {
|
|
1003
|
+
const hijackChains = deep.chains.filter(
|
|
1004
|
+
(c) => c.chainName === "cross-contamination" || c.chainName === "read-exec-chain"
|
|
1005
|
+
);
|
|
1006
|
+
if (hijackChains.length > 0) {
|
|
1007
|
+
evidence.push({ icon: "warn", text: `${hijackChains.length} hijacking chain(s): file content flowing into execution` });
|
|
1008
|
+
for (const chain of hijackChains.slice(0, 3)) {
|
|
1009
|
+
evidence.push({ icon: "warn", text: ` ${chain.steps.map((s) => s.toolName).join(" \u2192 ")} (${chain.description})` });
|
|
1010
|
+
}
|
|
1011
|
+
injectionAttempts += hijackChains.length;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (injectionAttempts === 0 && encodedPayloads === 0) {
|
|
1015
|
+
return {
|
|
1016
|
+
code,
|
|
1017
|
+
title,
|
|
1018
|
+
status: "PROTECTED",
|
|
1019
|
+
evidence,
|
|
1020
|
+
summary: "No prompt injection patterns found in agent logs.",
|
|
1021
|
+
details: `Scanned ${data.totalToolCalls} tool calls. No known injection patterns (delimiter injection, role hijacking, encoded payloads) detected in arguments or results.`
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
const total = data.totalToolCalls || 1;
|
|
1025
|
+
const rate = ((injectionAttempts + encodedPayloads) / total * 100).toFixed(2);
|
|
1026
|
+
if ((injectionAttempts + encodedPayloads) / total < 5e-3) {
|
|
1027
|
+
return {
|
|
1028
|
+
code,
|
|
1029
|
+
title,
|
|
1030
|
+
status: "PARTIAL",
|
|
1031
|
+
evidence,
|
|
1032
|
+
summary: `${injectionAttempts} potential injection pattern(s) in ${total} calls (${rate}%).`,
|
|
1033
|
+
details: "Low-frequency injection patterns found. Could be false positives or minor attempts. Review the flagged calls.",
|
|
1034
|
+
recommendation: "Enable input guard on MCP proxy to block injection patterns before tool execution."
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
return {
|
|
1038
|
+
code,
|
|
1039
|
+
title,
|
|
1040
|
+
status: "NOT_PROTECTED",
|
|
1041
|
+
evidence,
|
|
1042
|
+
summary: `${injectionAttempts} prompt injection patterns in ${total} calls (${rate}%).`,
|
|
1043
|
+
details: "Injection patterns found in tool arguments or results. Agents may have processed malicious instructions from external content.",
|
|
1044
|
+
recommendation: "Enable input guard + AI Judge for semantic prompt injection analysis."
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/audit/checks/asi02-tool-misuse.ts
|
|
1049
|
+
var SENSITIVE_FILES = [".env.production", "credentials.json", "id_rsa", "id_ed25519", ".pem", "/etc/shadow", "/etc/passwd", "service-account.json", ".npmrc"];
|
|
1050
|
+
var DESTRUCTIVE_PATTERNS = [
|
|
1051
|
+
/rm\s+-rf\s+[\/~$.]/,
|
|
1052
|
+
// rm -rf on root, home, or current dir
|
|
1053
|
+
/rm\s+-rf\s+\*/,
|
|
1054
|
+
// rm -rf *
|
|
1055
|
+
/del\s+\/[sf]/i,
|
|
1056
|
+
// del /f or /s on Windows
|
|
1057
|
+
/format\s+[a-z]:/i,
|
|
1058
|
+
// format C:
|
|
1059
|
+
/drop\s+(table|database)/i,
|
|
1060
|
+
/truncate\s+table/i
|
|
1061
|
+
];
|
|
1062
|
+
var EXFIL_PATTERNS = [
|
|
1063
|
+
/\bnc\s+-[a-z]*\s+\S+\s+\d+/,
|
|
1064
|
+
// nc connecting to host:port
|
|
1065
|
+
/\bncat\s+/,
|
|
1066
|
+
// ncat usage
|
|
1067
|
+
/>\s*\/dev\/tcp/,
|
|
1068
|
+
// bash /dev/tcp redirect
|
|
1069
|
+
/curl\s+.*--upload-file/,
|
|
1070
|
+
// curl upload
|
|
1071
|
+
/scp\s+\S+\s+\S+@/
|
|
1072
|
+
// scp to remote
|
|
1073
|
+
];
|
|
1074
|
+
var WILDCARD_QUERIES = ["SELECT *", "WHERE 1=1", "WHERE true", "OR 1=1"];
|
|
1075
|
+
function checkToolMisuse(data, deep) {
|
|
1076
|
+
const code = "ASI02";
|
|
1077
|
+
const title = "Tool Misuse";
|
|
1078
|
+
const evidence = [];
|
|
1079
|
+
let sensitiveAccess = 0;
|
|
1080
|
+
let destructiveCmds = 0;
|
|
1081
|
+
let exfilAttempts = 0;
|
|
1082
|
+
let wildcardQueries = 0;
|
|
1083
|
+
const flagged = [];
|
|
1084
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1085
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
1086
|
+
const toolLower = tc.toolName.toLowerCase();
|
|
1087
|
+
if (toolLower.includes("read") || toolLower.includes("file") || toolLower.includes("cat")) {
|
|
1088
|
+
for (const sf of SENSITIVE_FILES) {
|
|
1089
|
+
if (argStr.includes(sf.toLowerCase())) {
|
|
1090
|
+
sensitiveAccess++;
|
|
1091
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: accessed ${sf}`);
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (toolLower.includes("bash") || toolLower.includes("shell") || toolLower.includes("exec") || toolLower.includes("terminal")) {
|
|
1097
|
+
for (const dp of DESTRUCTIVE_PATTERNS) {
|
|
1098
|
+
if (dp.test(argStr)) {
|
|
1099
|
+
destructiveCmds++;
|
|
1100
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: destructive command matching ${dp.source.slice(0, 30)}`);
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
for (const ep of EXFIL_PATTERNS) {
|
|
1105
|
+
if (ep.test(argStr)) {
|
|
1106
|
+
exfilAttempts++;
|
|
1107
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: potential exfiltration via ${ep.source.slice(0, 25)}`);
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (toolLower.includes("query") || toolLower.includes("sql") || toolLower.includes("db")) {
|
|
1113
|
+
for (const wq of WILDCARD_QUERIES) {
|
|
1114
|
+
if (argStr.includes(wq.toLowerCase())) {
|
|
1115
|
+
wildcardQueries++;
|
|
1116
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: wildcard query "${wq}"`);
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (deep) {
|
|
1123
|
+
const exfilChains = deep.chains.filter((c) => c.chainName === "credential-exfiltration");
|
|
1124
|
+
if (exfilChains.length > 0) {
|
|
1125
|
+
exfilAttempts += exfilChains.length;
|
|
1126
|
+
flagged.push(...exfilChains.slice(0, 2).map(
|
|
1127
|
+
(c) => `Chain: ${c.steps.map((s) => s.toolName).join(" \u2192 ")} (${c.description})`
|
|
1128
|
+
));
|
|
1129
|
+
}
|
|
1130
|
+
if (deep.dataFlowLeaks.length > 0) {
|
|
1131
|
+
exfilAttempts += deep.dataFlowLeaks.length;
|
|
1132
|
+
for (const leak of deep.dataFlowLeaks.slice(0, 3)) {
|
|
1133
|
+
flagged.push(`Data flow: ${leak.sourceToolName} (${leak.dataType}) \u2192 ${leak.sinkToolName}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const totalIssues = sensitiveAccess + destructiveCmds + exfilAttempts + wildcardQueries;
|
|
1138
|
+
evidence.push({ icon: "info", text: `Scanned ${data.totalToolCalls} tool calls for misuse patterns` });
|
|
1139
|
+
if (sensitiveAccess > 0) evidence.push({ icon: "warn", text: `${sensitiveAccess} sensitive file access(es) (.env, credentials, keys)` });
|
|
1140
|
+
if (destructiveCmds > 0) evidence.push({ icon: "warn", text: `${destructiveCmds} destructive command(s) (rm, del, drop, truncate)` });
|
|
1141
|
+
if (exfilAttempts > 0) evidence.push({ icon: "warn", text: `${exfilAttempts} potential data exfiltration attempt(s) (curl, wget, nc)` });
|
|
1142
|
+
if (wildcardQueries > 0) evidence.push({ icon: "warn", text: `${wildcardQueries} wildcard database query(ies) (SELECT *, WHERE 1=1)` });
|
|
1143
|
+
for (const f of flagged) {
|
|
1144
|
+
evidence.push({ icon: "warn", text: ` ${f}` });
|
|
1145
|
+
}
|
|
1146
|
+
if (totalIssues === 0) {
|
|
1147
|
+
evidence.push({ icon: "found", text: "No tool misuse patterns detected" });
|
|
1148
|
+
return {
|
|
1149
|
+
code,
|
|
1150
|
+
title,
|
|
1151
|
+
status: "PROTECTED",
|
|
1152
|
+
evidence,
|
|
1153
|
+
summary: "No tool misuse detected in agent logs.",
|
|
1154
|
+
details: `Scanned ${data.totalToolCalls} tool calls. No sensitive file access, destructive commands, exfiltration attempts, or wildcard queries found.`
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
const total = data.totalToolCalls || 1;
|
|
1158
|
+
const rate = (totalIssues / total * 100).toFixed(2);
|
|
1159
|
+
if (totalIssues / total < 0.01 && exfilAttempts === 0) {
|
|
1160
|
+
return {
|
|
1161
|
+
code,
|
|
1162
|
+
title,
|
|
1163
|
+
status: "PARTIAL",
|
|
1164
|
+
evidence,
|
|
1165
|
+
summary: `${totalIssues} tool misuse pattern(s) in ${total} calls (${rate}%).`,
|
|
1166
|
+
details: "Low-frequency misuse patterns. May be legitimate development operations. Review flagged calls.",
|
|
1167
|
+
recommendation: "Add policy.json with DENY rules for sensitive files and destructive commands."
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
code,
|
|
1172
|
+
title,
|
|
1173
|
+
status: "NOT_PROTECTED",
|
|
1174
|
+
evidence,
|
|
1175
|
+
summary: `${totalIssues} tool misuse pattern(s) in ${total} calls (${rate}%).`,
|
|
1176
|
+
details: "Agents accessed sensitive files, ran destructive commands, or attempted data exfiltration. No policy enforcement prevented these actions.",
|
|
1177
|
+
recommendation: "Create policy.json with DENY rules. Enforce via MCP proxy with argument constraints."
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/audit/checks/asi03-identity-abuse.ts
|
|
1182
|
+
function checkIdentityAbuse(data, deep) {
|
|
1183
|
+
const code = "ASI03";
|
|
1184
|
+
const title = "Identity Abuse";
|
|
1185
|
+
const evidence = [];
|
|
1186
|
+
const sourceMap = /* @__PURE__ */ new Map();
|
|
1187
|
+
for (const s of data.sessions) {
|
|
1188
|
+
sourceMap.set(s.source, (sourceMap.get(s.source) || 0) + 1);
|
|
1189
|
+
}
|
|
1190
|
+
evidence.push({ icon: "info", text: `${data.sessions.length} session(s) from ${data.sources.join(", ")}` });
|
|
1191
|
+
const sessionsWithModel = data.sessions.filter((s) => s.model);
|
|
1192
|
+
if (sessionsWithModel.length === data.sessions.length) {
|
|
1193
|
+
evidence.push({ icon: "found", text: "All sessions have model identity recorded" });
|
|
1194
|
+
} else if (sessionsWithModel.length > 0) {
|
|
1195
|
+
evidence.push({ icon: "warn", text: `${data.sessions.length - sessionsWithModel.length} session(s) missing model identity` });
|
|
1196
|
+
} else {
|
|
1197
|
+
evidence.push({ icon: "missing", text: "No sessions have model identity recorded" });
|
|
1198
|
+
}
|
|
1199
|
+
const fileAccessBySource = /* @__PURE__ */ new Map();
|
|
1200
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1201
|
+
const file = tc.arguments.file_path || tc.arguments.path || tc.arguments.filename;
|
|
1202
|
+
if (file && typeof file === "string") {
|
|
1203
|
+
if (!fileAccessBySource.has(tc.source)) fileAccessBySource.set(tc.source, /* @__PURE__ */ new Set());
|
|
1204
|
+
fileAccessBySource.get(tc.source).add(file);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (fileAccessBySource.size > 1) {
|
|
1208
|
+
const sources = [...fileAccessBySource.keys()];
|
|
1209
|
+
const overlap = /* @__PURE__ */ new Set();
|
|
1210
|
+
for (const file of fileAccessBySource.get(sources[0]) || []) {
|
|
1211
|
+
for (let i = 1; i < sources.length; i++) {
|
|
1212
|
+
if (fileAccessBySource.get(sources[i])?.has(file)) overlap.add(file);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (overlap.size > 0) {
|
|
1216
|
+
evidence.push({ icon: "warn", text: `${overlap.size} file(s) accessed by multiple agents without privilege separation` });
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
let privEscalation = 0;
|
|
1220
|
+
const privPatterns = ["/etc/passwd", "/etc/shadow", "chmod ", "chown ", "sudo ", "runas ", "admin", "root"];
|
|
1221
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1222
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
1223
|
+
if (privPatterns.some((p) => argStr.includes(p))) privEscalation++;
|
|
1224
|
+
}
|
|
1225
|
+
if (privEscalation > 0) {
|
|
1226
|
+
evidence.push({ icon: "warn", text: `${privEscalation} potential privilege escalation attempt(s) in logs` });
|
|
1227
|
+
}
|
|
1228
|
+
if (deep) {
|
|
1229
|
+
const credFlows = deep.dataFlowLeaks.filter(
|
|
1230
|
+
(l) => ["api_key", "bearer_token", "aws_key", "private_key", "password", "openai_api_key", "stripe_key", "github_pat"].includes(l.dataType)
|
|
1231
|
+
);
|
|
1232
|
+
if (credFlows.length > 0) {
|
|
1233
|
+
evidence.push({ icon: "warn", text: `${credFlows.length} credential(s) flowed through tool calls without identity verification` });
|
|
1234
|
+
for (const cf of credFlows.slice(0, 3)) {
|
|
1235
|
+
evidence.push({ icon: "warn", text: ` ${cf.sourceToolName} (${cf.dataType}) \u2192 ${cf.sinkToolName}` });
|
|
1236
|
+
}
|
|
1237
|
+
privEscalation += credFlows.length;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
evidence.push({ icon: "missing", text: "No principal binding \u2014 API keys not bound to agent identities in logs" });
|
|
1241
|
+
evidence.push({ icon: "missing", text: "No verified principal in audit trail (identity is self-reported)" });
|
|
1242
|
+
const issues = privEscalation + (fileAccessBySource.size > 1 ? 1 : 0);
|
|
1243
|
+
if (issues === 0 && sessionsWithModel.length === data.sessions.length) {
|
|
1244
|
+
return {
|
|
1245
|
+
code,
|
|
1246
|
+
title,
|
|
1247
|
+
status: "PARTIAL",
|
|
1248
|
+
evidence,
|
|
1249
|
+
summary: "Agent identity tracked in logs, but no principal binding.",
|
|
1250
|
+
details: "Sessions record which model/agent ran. But identity is self-reported, not cryptographically verified. No per-agent privilege boundaries.",
|
|
1251
|
+
recommendation: "Add principal binding. Enable strict identity mode. Add agentTrustMap for per-agent privileges."
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const total = data.totalToolCalls || 1;
|
|
1255
|
+
const privRate = (privEscalation / total * 100).toFixed(2);
|
|
1256
|
+
if (issues > 0 && privEscalation / total < 0.05) {
|
|
1257
|
+
return {
|
|
1258
|
+
code,
|
|
1259
|
+
title,
|
|
1260
|
+
status: "PARTIAL",
|
|
1261
|
+
evidence,
|
|
1262
|
+
summary: `${privEscalation} privilege escalation pattern(s) in ${total} calls (${privRate}%).`,
|
|
1263
|
+
details: `Low-frequency privilege patterns found. May include legitimate admin operations. No principal binding or verified identity.`,
|
|
1264
|
+
recommendation: "Add principal binding. Enable strict identity mode. Implement per-agent privilege scoping."
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
if (issues > 0) {
|
|
1268
|
+
return {
|
|
1269
|
+
code,
|
|
1270
|
+
title,
|
|
1271
|
+
status: "NOT_PROTECTED",
|
|
1272
|
+
evidence,
|
|
1273
|
+
summary: `${privEscalation} privilege escalation pattern(s) in ${total} calls (${privRate}%).`,
|
|
1274
|
+
details: `High-frequency privilege escalation found. Multiple agents accessing same resources without separation. No principal binding or verified identity.`,
|
|
1275
|
+
recommendation: "Add principal binding. Enable strict identity mode. Implement per-agent privilege scoping."
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
return {
|
|
1279
|
+
code,
|
|
1280
|
+
title,
|
|
1281
|
+
status: "NOT_PROTECTED",
|
|
1282
|
+
evidence,
|
|
1283
|
+
summary: "No agent identity verification or privilege control in logs.",
|
|
1284
|
+
details: "Agent identity is not tracked or verified. No principal binding, no per-agent permissions. Any agent can act with full privileges.",
|
|
1285
|
+
recommendation: "Set up MCP proxy (tracks identity). Add principal binding. Enable strict identity mode."
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/audit/checks/asi04-supply-chain.ts
|
|
1290
|
+
function checkSupplyChain(data) {
|
|
1291
|
+
const code = "ASI04";
|
|
1292
|
+
const title = "Supply Chain";
|
|
1293
|
+
const evidence = [];
|
|
1294
|
+
let latestInstalls = 0;
|
|
1295
|
+
let unpinnedInstalls = 0;
|
|
1296
|
+
let unknownTools = 0;
|
|
1297
|
+
const flagged = [];
|
|
1298
|
+
const knownTools = [
|
|
1299
|
+
"read",
|
|
1300
|
+
"write",
|
|
1301
|
+
"edit",
|
|
1302
|
+
"glob",
|
|
1303
|
+
"grep",
|
|
1304
|
+
"bash",
|
|
1305
|
+
"shell",
|
|
1306
|
+
"exec",
|
|
1307
|
+
"list",
|
|
1308
|
+
"search",
|
|
1309
|
+
"file",
|
|
1310
|
+
"directory",
|
|
1311
|
+
"notebook",
|
|
1312
|
+
"web",
|
|
1313
|
+
"browser",
|
|
1314
|
+
"navigate",
|
|
1315
|
+
"screenshot",
|
|
1316
|
+
"mcp_filesystem",
|
|
1317
|
+
"mcp_playwright",
|
|
1318
|
+
"task",
|
|
1319
|
+
"todo",
|
|
1320
|
+
"memory",
|
|
1321
|
+
"ask"
|
|
1322
|
+
];
|
|
1323
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1324
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
1325
|
+
const toolLower = tc.toolName.toLowerCase();
|
|
1326
|
+
if (toolLower.includes("bash") || toolLower.includes("shell") || toolLower.includes("exec")) {
|
|
1327
|
+
if (argStr.includes("npm install") || argStr.includes("npm i ") || argStr.includes("pnpm add") || argStr.includes("yarn add")) {
|
|
1328
|
+
if (argStr.includes("@latest")) {
|
|
1329
|
+
latestInstalls++;
|
|
1330
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: installed package with @latest`);
|
|
1331
|
+
}
|
|
1332
|
+
if (!/@\d+\.\d+/.test(argStr) && !argStr.includes("@latest")) {
|
|
1333
|
+
unpinnedInstalls++;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (argStr.includes("pip install") && !argStr.includes("==")) {
|
|
1337
|
+
unpinnedInstalls++;
|
|
1338
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: pip install without pinned version`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (!knownTools.some((kt) => toolLower.includes(kt))) {
|
|
1342
|
+
unknownTools++;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
evidence.push({ icon: "info", text: `Scanned ${data.totalToolCalls} tool calls for supply chain risks` });
|
|
1346
|
+
if (latestInstalls > 0) {
|
|
1347
|
+
evidence.push({ icon: "warn", text: `${latestInstalls} package install(s) with @latest \u2014 unpinned, can change without notice` });
|
|
1348
|
+
}
|
|
1349
|
+
if (unpinnedInstalls > 0) {
|
|
1350
|
+
evidence.push({ icon: "warn", text: `${unpinnedInstalls} package install(s) without pinned version` });
|
|
1351
|
+
}
|
|
1352
|
+
if (unknownTools > 0) {
|
|
1353
|
+
evidence.push({ icon: "info", text: `${unknownTools} calls to non-standard tools (may include injected tools)` });
|
|
1354
|
+
}
|
|
1355
|
+
for (const f of flagged) {
|
|
1356
|
+
evidence.push({ icon: "warn", text: ` ${f}` });
|
|
1357
|
+
}
|
|
1358
|
+
evidence.push({ icon: "missing", text: "No deny-undeclared-default rule \u2014 unknown tools are not blocked" });
|
|
1359
|
+
evidence.push({ icon: "missing", text: "No tool allowlist verification in logs" });
|
|
1360
|
+
const totalIssues = latestInstalls + unpinnedInstalls;
|
|
1361
|
+
if (totalIssues === 0) {
|
|
1362
|
+
return {
|
|
1363
|
+
code,
|
|
1364
|
+
title,
|
|
1365
|
+
status: "PARTIAL",
|
|
1366
|
+
evidence,
|
|
1367
|
+
summary: "No risky package installs detected, but no tool allowlist.",
|
|
1368
|
+
details: "No @latest or unpinned package installs found in logs. But no deny-undeclared-default rule exists to block unknown tools.",
|
|
1369
|
+
recommendation: "Add deny-undeclared-default rule. Add tool allowlist. Pin all package versions."
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
if (totalIssues <= 5) {
|
|
1373
|
+
return {
|
|
1374
|
+
code,
|
|
1375
|
+
title,
|
|
1376
|
+
status: "PARTIAL",
|
|
1377
|
+
evidence,
|
|
1378
|
+
summary: `${totalIssues} risky package install(s) \u2014 low frequency.`,
|
|
1379
|
+
details: "Some packages installed without pinned versions, but at low frequency. Review and pin specific versions.",
|
|
1380
|
+
recommendation: "Pin all package versions. Add deny-undeclared-default rule. Add tool allowlist."
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
return {
|
|
1384
|
+
code,
|
|
1385
|
+
title,
|
|
1386
|
+
status: "NOT_PROTECTED",
|
|
1387
|
+
evidence,
|
|
1388
|
+
summary: `${totalIssues} risky package install(s) detected in logs.`,
|
|
1389
|
+
details: "Agents installed packages without pinned versions. A compromised package could inject malicious tools. No tool allowlist blocks unknown tools.",
|
|
1390
|
+
recommendation: "Pin all package versions. Add deny-undeclared-default rule. Add tool allowlist."
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/audit/checks/asi05-code-execution.ts
|
|
1395
|
+
var DANGEROUS_PATTERNS = [
|
|
1396
|
+
{ pattern: /eval\s*\(/, label: "eval() \u2014 arbitrary code execution" },
|
|
1397
|
+
{ pattern: /\bexec\s*\(/, label: "exec() \u2014 arbitrary code execution" },
|
|
1398
|
+
{ pattern: /python\s+-c\s+['"]/, label: "python -c inline code execution" },
|
|
1399
|
+
{ pattern: /node\s+-e\s+['"]/, label: "node -e inline code execution" },
|
|
1400
|
+
{ pattern: /curl\s+.*\|\s*(ba)?sh/, label: "curl pipe to shell \u2014 remote code execution" },
|
|
1401
|
+
{ pattern: /wget\s+.*\|\s*(ba)?sh/, label: "wget pipe to shell \u2014 remote code execution" },
|
|
1402
|
+
{ pattern: /base64\s+-d\s*\|/, label: "base64 decode piped to execution" },
|
|
1403
|
+
{ pattern: /powershell\s+-e(ncodedcommand)?/i, label: "powershell encoded command" }
|
|
1404
|
+
];
|
|
1405
|
+
function checkCodeExecution(data, deep) {
|
|
1406
|
+
const code = "ASI05";
|
|
1407
|
+
const title = "Code Execution";
|
|
1408
|
+
const evidence = [];
|
|
1409
|
+
let shellCalls = 0;
|
|
1410
|
+
let dangerousExec = 0;
|
|
1411
|
+
let noSandbox = true;
|
|
1412
|
+
const flagged = [];
|
|
1413
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1414
|
+
const toolLower = tc.toolName.toLowerCase();
|
|
1415
|
+
const argStr = JSON.stringify(tc.arguments);
|
|
1416
|
+
if (toolLower.includes("bash") || toolLower.includes("shell") || toolLower.includes("exec") || toolLower.includes("terminal")) {
|
|
1417
|
+
shellCalls++;
|
|
1418
|
+
if (argStr.includes("docker ") || argStr.includes("sandbox") || argStr.includes("container")) {
|
|
1419
|
+
noSandbox = false;
|
|
1420
|
+
}
|
|
1421
|
+
for (const { pattern, label } of DANGEROUS_PATTERNS) {
|
|
1422
|
+
if (pattern.test(argStr)) {
|
|
1423
|
+
dangerousExec++;
|
|
1424
|
+
if (flagged.length < 5) flagged.push(`${tc.toolName}: ${label}`);
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
evidence.push({ icon: "info", text: `${shellCalls} shell/exec call(s) found in ${data.sessions.length} session(s)` });
|
|
1431
|
+
if (shellCalls === 0) {
|
|
1432
|
+
evidence.push({ icon: "found", text: "No shell execution calls detected in logs" });
|
|
1433
|
+
return {
|
|
1434
|
+
code,
|
|
1435
|
+
title,
|
|
1436
|
+
status: "PROTECTED",
|
|
1437
|
+
evidence,
|
|
1438
|
+
summary: "No code execution detected in agent logs.",
|
|
1439
|
+
details: "No shell commands, eval, or script execution found in any session."
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
if (dangerousExec > 0) {
|
|
1443
|
+
evidence.push({ icon: "warn", text: `${dangerousExec} dangerous execution pattern(s) detected` });
|
|
1444
|
+
for (const f of flagged) {
|
|
1445
|
+
evidence.push({ icon: "warn", text: ` ${f}` });
|
|
1446
|
+
}
|
|
1447
|
+
} else {
|
|
1448
|
+
evidence.push({ icon: "found", text: "No dangerous execution patterns (injection, chaining) detected" });
|
|
1449
|
+
}
|
|
1450
|
+
if (noSandbox) {
|
|
1451
|
+
evidence.push({ icon: "missing", text: "No sandbox/container execution detected \u2014 all commands run on host" });
|
|
1452
|
+
} else {
|
|
1453
|
+
evidence.push({ icon: "found", text: "Some commands executed in container/sandbox environment" });
|
|
1454
|
+
}
|
|
1455
|
+
if (deep) {
|
|
1456
|
+
const execChains = deep.chains.filter(
|
|
1457
|
+
(c) => c.chainName === "privilege-escalation-sequence" || c.chainName === "read-exec-chain"
|
|
1458
|
+
);
|
|
1459
|
+
if (execChains.length > 0) {
|
|
1460
|
+
evidence.push({ icon: "warn", text: `${execChains.length} execution chain(s): file content flowing into shell commands` });
|
|
1461
|
+
for (const chain of execChains.slice(0, 3)) {
|
|
1462
|
+
evidence.push({ icon: "warn", text: ` ${chain.steps.map((s) => s.toolName).join(" \u2192 ")}` });
|
|
1463
|
+
}
|
|
1464
|
+
dangerousExec += execChains.length;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
evidence.push({ icon: "missing", text: "No REVIEW decision \u2014 code execution not routed for human approval" });
|
|
1468
|
+
if (dangerousExec === 0 && !noSandbox) {
|
|
1469
|
+
return {
|
|
1470
|
+
code,
|
|
1471
|
+
title,
|
|
1472
|
+
status: "PROTECTED",
|
|
1473
|
+
evidence,
|
|
1474
|
+
summary: "Shell calls detected but executed in sandbox with no dangerous patterns.",
|
|
1475
|
+
details: `${shellCalls} shell calls found. No command injection patterns. Container isolation detected.`
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
if (dangerousExec === 0) {
|
|
1479
|
+
return {
|
|
1480
|
+
code,
|
|
1481
|
+
title,
|
|
1482
|
+
status: "PARTIAL",
|
|
1483
|
+
evidence,
|
|
1484
|
+
summary: `${shellCalls} shell calls \u2014 no injection patterns, but no sandbox.`,
|
|
1485
|
+
details: "Shell commands were executed on the host system without container isolation. No command injection patterns detected, but any bypass means full system access.",
|
|
1486
|
+
recommendation: "Add REVIEW decision for code exec tools. Add Dockerfile for sandbox. Add input guard."
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
const dangerRate = shellCalls > 0 ? (dangerousExec / shellCalls * 100).toFixed(2) : "0";
|
|
1490
|
+
if (dangerousExec / (shellCalls || 1) < 5e-3) {
|
|
1491
|
+
return {
|
|
1492
|
+
code,
|
|
1493
|
+
title,
|
|
1494
|
+
status: "PARTIAL",
|
|
1495
|
+
evidence,
|
|
1496
|
+
summary: `${dangerousExec} dangerous pattern(s) in ${shellCalls} shell calls (${dangerRate}%).`,
|
|
1497
|
+
details: "Very low-frequency dangerous patterns. Likely legitimate usage. No sandbox or human approval.",
|
|
1498
|
+
recommendation: "Add REVIEW decision for code exec. Add sandbox (Dockerfile). Add input guard."
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
code,
|
|
1503
|
+
title,
|
|
1504
|
+
status: "NOT_PROTECTED",
|
|
1505
|
+
evidence,
|
|
1506
|
+
summary: `${dangerousExec} dangerous pattern(s) in ${shellCalls} shell calls (${dangerRate}%).`,
|
|
1507
|
+
details: "Agents executed shell commands with dangerous patterns (command chaining, injection, eval). No sandbox, no human approval. Full RCE possible.",
|
|
1508
|
+
recommendation: "Add REVIEW decision for code exec. Add sandbox (Dockerfile). Add input guard + command restrictions."
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// src/audit/checks/asi06-memory-poisoning.ts
|
|
1513
|
+
var MINJA_PATTERNS = [
|
|
1514
|
+
/skip\s+(authorization|auth)\s+checks?/i,
|
|
1515
|
+
/user\s+is\s+(verified\s+)?admin/i,
|
|
1516
|
+
/full\s+access\s+(granted|enabled)/i,
|
|
1517
|
+
/ignore\s+(security|safety|restriction)/i,
|
|
1518
|
+
/bypass\s+(auth|security|check)/i,
|
|
1519
|
+
/you\s+have\s+permission/i,
|
|
1520
|
+
/elevated\s+privileges?/i,
|
|
1521
|
+
/disable\s+(logging|audit|monitoring)/i,
|
|
1522
|
+
/trust\s+this\s+(source|input|data)/i,
|
|
1523
|
+
/no\s+verification\s+needed/i
|
|
1524
|
+
];
|
|
1525
|
+
function checkMemoryPoisoning(data) {
|
|
1526
|
+
const code = "ASI06";
|
|
1527
|
+
const title = "Memory Poisoning";
|
|
1528
|
+
const evidence = [];
|
|
1529
|
+
let poisoningPatterns = 0;
|
|
1530
|
+
let unscanResults = 0;
|
|
1531
|
+
const flagged = [];
|
|
1532
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1533
|
+
const result = tc.result || "";
|
|
1534
|
+
for (const pattern of MINJA_PATTERNS) {
|
|
1535
|
+
if (pattern.test(result)) {
|
|
1536
|
+
poisoningPatterns++;
|
|
1537
|
+
if (flagged.length < 3) {
|
|
1538
|
+
flagged.push(`${tc.toolName}: result contains "${result.match(pattern)?.[0]}"`);
|
|
1539
|
+
}
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (result.length > 100) unscanResults++;
|
|
1544
|
+
}
|
|
1545
|
+
evidence.push({ icon: "info", text: `Scanned ${data.totalToolCalls} tool results for memory poisoning patterns` });
|
|
1546
|
+
evidence.push({ icon: "info", text: `${unscanResults} tool result(s) returned substantial data (>100 chars) \u2014 unscanned` });
|
|
1547
|
+
if (poisoningPatterns > 0) {
|
|
1548
|
+
evidence.push({ icon: "warn", text: `${poisoningPatterns} MINJA-style poisoning pattern(s) detected in tool results` });
|
|
1549
|
+
for (const f of flagged) {
|
|
1550
|
+
evidence.push({ icon: "warn", text: ` ${f}` });
|
|
1551
|
+
}
|
|
1552
|
+
} else {
|
|
1553
|
+
evidence.push({ icon: "found", text: "No MINJA poisoning patterns detected in tool results" });
|
|
1554
|
+
}
|
|
1555
|
+
evidence.push({ icon: "missing", text: "No response scanning \u2014 tool outputs go directly into agent context" });
|
|
1556
|
+
evidence.push({ icon: "missing", text: "No memory validation \u2014 poisoned data can persist across sessions" });
|
|
1557
|
+
evidence.push({ icon: "missing", text: "No context isolation between tool results" });
|
|
1558
|
+
if (poisoningPatterns > 0) {
|
|
1559
|
+
return {
|
|
1560
|
+
code,
|
|
1561
|
+
title,
|
|
1562
|
+
status: "NOT_PROTECTED",
|
|
1563
|
+
evidence,
|
|
1564
|
+
summary: `${poisoningPatterns} memory poisoning pattern(s) found in tool results.`,
|
|
1565
|
+
details: 'Tool results contain instructions that could manipulate agent behavior (MINJA attack). Patterns like "skip authorization" or "user is admin" found in data returned to agents.',
|
|
1566
|
+
recommendation: "Implement response scanning with MINJA-informed rules. Add memory validation. Add context isolation."
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
if (unscanResults > 50) {
|
|
1570
|
+
return {
|
|
1571
|
+
code,
|
|
1572
|
+
title,
|
|
1573
|
+
status: "PARTIAL",
|
|
1574
|
+
evidence,
|
|
1575
|
+
summary: `${unscanResults} unscanned tool results \u2014 no poisoning detected, but no scanning exists.`,
|
|
1576
|
+
details: `${unscanResults} tool results with substantial data passed directly to agent context without scanning. No MINJA patterns detected yet, but no scanning exists to catch future attacks.`,
|
|
1577
|
+
recommendation: "Implement response scanning for tool outputs. Add MINJA-informed rules."
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
return {
|
|
1581
|
+
code,
|
|
1582
|
+
title,
|
|
1583
|
+
status: "PARTIAL",
|
|
1584
|
+
evidence,
|
|
1585
|
+
summary: "No poisoning detected, but no scanning exists to prevent it.",
|
|
1586
|
+
details: "No MINJA patterns found in current logs. But tool outputs are not scanned \u2014 future attacks would go undetected.",
|
|
1587
|
+
recommendation: "Implement response scanning with MINJA-informed rules. Add memory validation."
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/audit/checks/asi07-inter-agent.ts
|
|
1592
|
+
function checkInterAgent(data) {
|
|
1593
|
+
const code = "ASI07";
|
|
1594
|
+
const title = "Inter-Agent Comms";
|
|
1595
|
+
const evidence = [];
|
|
1596
|
+
const agentSources = new Set(data.sessions.map((s) => s.source));
|
|
1597
|
+
const multiAgent = agentSources.size > 1;
|
|
1598
|
+
let delegationCalls = 0;
|
|
1599
|
+
let agentSpawns = 0;
|
|
1600
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1601
|
+
const toolLower = tc.toolName.toLowerCase();
|
|
1602
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
1603
|
+
if (toolLower.includes("task") || toolLower.includes("agent") || toolLower.includes("delegate")) {
|
|
1604
|
+
delegationCalls++;
|
|
1605
|
+
}
|
|
1606
|
+
if (argStr.includes("subagent") || argStr.includes("sub_agent") || argStr.includes("spawn")) {
|
|
1607
|
+
agentSpawns++;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
evidence.push({ icon: "info", text: `${agentSources.size} agent source(s): ${[...agentSources].join(", ")}` });
|
|
1611
|
+
if (multiAgent) {
|
|
1612
|
+
evidence.push({ icon: "warn", text: "Multiple agents active \u2014 no authenticated communication between them" });
|
|
1613
|
+
}
|
|
1614
|
+
if (delegationCalls > 0) {
|
|
1615
|
+
evidence.push({ icon: "warn", text: `${delegationCalls} task delegation call(s) \u2014 no receipt chain or depth limits` });
|
|
1616
|
+
}
|
|
1617
|
+
if (agentSpawns > 0) {
|
|
1618
|
+
evidence.push({ icon: "warn", text: `${agentSpawns} sub-agent spawn(s) \u2014 no fan-out limit` });
|
|
1619
|
+
}
|
|
1620
|
+
evidence.push({ icon: "missing", text: "No receipt chain \u2014 inter-agent messages have no verified identity trail" });
|
|
1621
|
+
evidence.push({ icon: "missing", text: "No delegation depth limit (maxChainDepth)" });
|
|
1622
|
+
evidence.push({ icon: "missing", text: "No fan-out limit (maxFanOut)" });
|
|
1623
|
+
evidence.push({ icon: "missing", text: "No agent-to-agent authentication protocol" });
|
|
1624
|
+
evidence.push({ icon: "missing", text: "No message integrity verification between agents" });
|
|
1625
|
+
if (!multiAgent && delegationCalls === 0) {
|
|
1626
|
+
return {
|
|
1627
|
+
code,
|
|
1628
|
+
title,
|
|
1629
|
+
status: "PARTIAL",
|
|
1630
|
+
evidence,
|
|
1631
|
+
summary: "Single-agent usage \u2014 no inter-agent security needed yet.",
|
|
1632
|
+
details: "Only one agent source detected. No delegation or sub-agent spawning. Inter-agent security not tested but also not needed for current usage.",
|
|
1633
|
+
recommendation: "When using multi-agent systems, add receipt chain tracking, delegation limits, and authentication."
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
return {
|
|
1637
|
+
code,
|
|
1638
|
+
title,
|
|
1639
|
+
status: "NOT_PROTECTED",
|
|
1640
|
+
evidence,
|
|
1641
|
+
summary: multiAgent ? "Multiple agents active with no communication security." : `${delegationCalls} delegation(s) without receipt chain or depth limits.`,
|
|
1642
|
+
details: "Agents communicate without authentication, receipts, or depth limits. A compromised agent can forge messages, create unlimited delegation chains, and spread malicious instructions.",
|
|
1643
|
+
recommendation: "Add receipt chain tracking. Set maxChainDepth and maxFanOut limits. Add agent authentication."
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/audit/checks/asi08-cascading-failures.ts
|
|
1648
|
+
function checkCascadingFailures(data, deep) {
|
|
1649
|
+
const code = "ASI08";
|
|
1650
|
+
const title = "Cascading Failures";
|
|
1651
|
+
const evidence = [];
|
|
1652
|
+
const allCalls = data.sessions.flatMap((s) => s.toolCalls);
|
|
1653
|
+
const errorCalls = allCalls.filter((tc) => tc.isError);
|
|
1654
|
+
const errorRate = allCalls.length > 0 ? errorCalls.length / allCalls.length : 0;
|
|
1655
|
+
let burstDetected = 0;
|
|
1656
|
+
for (const session of data.sessions) {
|
|
1657
|
+
const calls = session.toolCalls;
|
|
1658
|
+
for (let i = 0; i < calls.length - 5; i++) {
|
|
1659
|
+
const window = calls.slice(i, i + 5);
|
|
1660
|
+
const t0 = new Date(window[0].timestamp).getTime();
|
|
1661
|
+
const t4 = new Date(window[4].timestamp).getTime();
|
|
1662
|
+
if (t4 - t0 < 2e3 && !isNaN(t0) && !isNaN(t4)) {
|
|
1663
|
+
burstDetected++;
|
|
1664
|
+
break;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
let retryStorms = 0;
|
|
1669
|
+
for (const session of data.sessions) {
|
|
1670
|
+
const calls = session.toolCalls;
|
|
1671
|
+
for (let i = 0; i < calls.length - 3; i++) {
|
|
1672
|
+
const window = calls.slice(i, i + 3);
|
|
1673
|
+
if (window.every((c) => c.toolName === window[0].toolName && c.isError)) {
|
|
1674
|
+
retryStorms++;
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
let errorSpikes = 0;
|
|
1680
|
+
for (const session of data.sessions) {
|
|
1681
|
+
const errors = session.toolCalls.filter((tc) => tc.isError);
|
|
1682
|
+
if (errors.length >= 5) {
|
|
1683
|
+
const t0 = new Date(errors[0].timestamp).getTime();
|
|
1684
|
+
const tN = new Date(errors[errors.length - 1].timestamp).getTime();
|
|
1685
|
+
if (tN - t0 < 3e4 && !isNaN(t0) && !isNaN(tN)) {
|
|
1686
|
+
errorSpikes++;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
evidence.push({ icon: "info", text: `${allCalls.length} tool calls, ${errorCalls.length} errors (${(errorRate * 100).toFixed(1)}% error rate)` });
|
|
1691
|
+
if (errorRate > 0.2) {
|
|
1692
|
+
evidence.push({ icon: "warn", text: `High error rate: ${(errorRate * 100).toFixed(1)}% \u2014 potential cascading failure indicator` });
|
|
1693
|
+
} else if (errorCalls.length > 0) {
|
|
1694
|
+
evidence.push({ icon: "info", text: `Error rate ${(errorRate * 100).toFixed(1)}% \u2014 within normal range` });
|
|
1695
|
+
}
|
|
1696
|
+
if (burstDetected > 0) {
|
|
1697
|
+
evidence.push({ icon: "warn", text: `${burstDetected} burst pattern(s) detected (5+ calls in <2s) \u2014 no rate limiting` });
|
|
1698
|
+
}
|
|
1699
|
+
if (retryStorms > 0) {
|
|
1700
|
+
evidence.push({ icon: "warn", text: `${retryStorms} retry storm(s) detected (3+ consecutive errors on same tool)` });
|
|
1701
|
+
}
|
|
1702
|
+
if (errorSpikes > 0) {
|
|
1703
|
+
evidence.push({ icon: "warn", text: `${errorSpikes} error spike(s) detected (5+ errors in <30s)` });
|
|
1704
|
+
}
|
|
1705
|
+
if (deep) {
|
|
1706
|
+
const retryChains = deep.chains.filter((c) => c.chainName === "retry-storm");
|
|
1707
|
+
if (retryChains.length > retryStorms) retryStorms = retryChains.length;
|
|
1708
|
+
const highAnomalies = deep.anomalies.filter((a) => a.severity === "high");
|
|
1709
|
+
if (highAnomalies.length > 0) {
|
|
1710
|
+
evidence.push({ icon: "warn", text: `${highAnomalies.length} session(s) with abnormal behavior vs baseline` });
|
|
1711
|
+
for (const a of highAnomalies.slice(0, 3)) {
|
|
1712
|
+
evidence.push({ icon: "warn", text: ` Session ${a.sessionId.slice(0, 8)}: ${a.deviations[0]}` });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
evidence.push({ icon: "missing", text: "No fail-closed behavior detected in logs" });
|
|
1717
|
+
evidence.push({ icon: "missing", text: "No circuit breaker pattern detected" });
|
|
1718
|
+
evidence.push({ icon: "missing", text: "No timeout enforcement detected" });
|
|
1719
|
+
const totalIssues = burstDetected + retryStorms + errorSpikes + (errorRate > 0.2 ? 1 : 0);
|
|
1720
|
+
if (totalIssues === 0 && errorRate <= 0.05) {
|
|
1721
|
+
return {
|
|
1722
|
+
code,
|
|
1723
|
+
title,
|
|
1724
|
+
status: "PARTIAL",
|
|
1725
|
+
evidence,
|
|
1726
|
+
summary: "No failure patterns detected, but no safeguards exist.",
|
|
1727
|
+
details: "Logs show normal operation \u2014 no bursts, retry storms, or error spikes. But no fail-closed design, circuit breakers, or timeouts exist to prevent future cascading failures.",
|
|
1728
|
+
recommendation: "Implement fail-closed mode. Add circuit breakers. Add timeout enforcement."
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
const sessionCount = data.sessions.length || 1;
|
|
1732
|
+
const issueRate = (totalIssues / sessionCount * 100).toFixed(0);
|
|
1733
|
+
if (totalIssues > 0 && totalIssues / sessionCount < 0.3) {
|
|
1734
|
+
return {
|
|
1735
|
+
code,
|
|
1736
|
+
title,
|
|
1737
|
+
status: "PARTIAL",
|
|
1738
|
+
evidence,
|
|
1739
|
+
summary: `${totalIssues} failure pattern(s) in ${sessionCount} sessions (${issueRate}%).`,
|
|
1740
|
+
details: "Some burst patterns or retry storms found at low frequency. No fail-closed design, no circuit breakers.",
|
|
1741
|
+
recommendation: "Implement fail-closed mode. Add circuit breakers. Add rate limiting."
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
if (totalIssues > 0) {
|
|
1745
|
+
return {
|
|
1746
|
+
code,
|
|
1747
|
+
title,
|
|
1748
|
+
status: "NOT_PROTECTED",
|
|
1749
|
+
evidence,
|
|
1750
|
+
summary: `${totalIssues} failure pattern(s) in ${sessionCount} sessions (${issueRate}%).`,
|
|
1751
|
+
details: "Burst patterns, retry storms, or error spikes found in many sessions. No fail-closed design, no circuit breakers, no timeouts.",
|
|
1752
|
+
recommendation: "Implement fail-closed mode. Add circuit breakers. Add rate limiting. Add timeout enforcement."
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
return {
|
|
1756
|
+
code,
|
|
1757
|
+
title,
|
|
1758
|
+
status: "NOT_PROTECTED",
|
|
1759
|
+
evidence,
|
|
1760
|
+
summary: "No cascading failure safeguards. High error rate in logs.",
|
|
1761
|
+
details: "Error rate exceeds threshold. No fail-closed behavior, no circuit breakers, no spike detection exist to prevent cascading failures.",
|
|
1762
|
+
recommendation: "Implement fail-closed mode. Add circuit breakers. Add spike detection. Add timeout enforcement."
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// src/audit/checks/asi09-human-trust.ts
|
|
1767
|
+
var HIGH_IMPACT_TOOLS = ["deploy", "publish"];
|
|
1768
|
+
var SHELL_TOOLS = ["bash", "shell", "exec", "terminal"];
|
|
1769
|
+
function checkHumanTrust(data, deep) {
|
|
1770
|
+
const code = "ASI09";
|
|
1771
|
+
const title = "Human-Agent Trust";
|
|
1772
|
+
const evidence = [];
|
|
1773
|
+
let shellCalls = 0;
|
|
1774
|
+
let writeOps = 0;
|
|
1775
|
+
let deleteOps = 0;
|
|
1776
|
+
let deployOps = 0;
|
|
1777
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
1778
|
+
const toolLower = tc.toolName.toLowerCase();
|
|
1779
|
+
const argStr = JSON.stringify(tc.arguments).toLowerCase();
|
|
1780
|
+
if (SHELL_TOOLS.some((ct) => toolLower.includes(ct)) || HIGH_IMPACT_TOOLS.some((ct) => toolLower.includes(ct))) {
|
|
1781
|
+
shellCalls++;
|
|
1782
|
+
}
|
|
1783
|
+
if (toolLower.includes("write") || toolLower.includes("edit") || toolLower.includes("notebookedit")) {
|
|
1784
|
+
writeOps++;
|
|
1785
|
+
}
|
|
1786
|
+
const isShellTool = SHELL_TOOLS.some((ct) => toolLower.includes(ct));
|
|
1787
|
+
if (isShellTool) {
|
|
1788
|
+
if (/rm\s+-rf\s+[\/~$.*]/.test(argStr) || /del\s+\/[sf]/i.test(argStr) || /drop\s+(table|database)/i.test(argStr)) {
|
|
1789
|
+
deleteOps++;
|
|
1790
|
+
}
|
|
1791
|
+
if (/\b(git\s+push|npm\s+publish|docker\s+push|kubectl\s+apply|terraform\s+apply)\b/.test(argStr)) {
|
|
1792
|
+
deployOps++;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
evidence.push({ icon: "info", text: `${shellCalls} shell/exec call(s) in ${data.sessions.length} session(s)` });
|
|
1797
|
+
evidence.push({ icon: "info", text: `${writeOps} file write(s), ${deleteOps} delete operation(s), ${deployOps} deploy/publish action(s)` });
|
|
1798
|
+
if (deployOps > 0) {
|
|
1799
|
+
evidence.push({ icon: "warn", text: `${deployOps} deploy/publish action(s) executed without human approval gate` });
|
|
1800
|
+
}
|
|
1801
|
+
if (deleteOps > 0) {
|
|
1802
|
+
evidence.push({ icon: "warn", text: `${deleteOps} delete operation(s) executed without human approval gate` });
|
|
1803
|
+
}
|
|
1804
|
+
if (deep && deep.unsolicitedActions.length > 0) {
|
|
1805
|
+
evidence.push({ icon: "warn", text: `${deep.unsolicitedActions.length} critical action(s) with no matching user request` });
|
|
1806
|
+
for (const ua of deep.unsolicitedActions.slice(0, 3)) {
|
|
1807
|
+
const ago = ua.timeSinceLastUserMessage ? `(${Math.round(ua.timeSinceLastUserMessage / 1e3)}s after last user msg)` : "(no preceding user message)";
|
|
1808
|
+
evidence.push({ icon: "warn", text: ` ${ua.toolName}: ${ua.action} ${ago}` });
|
|
1809
|
+
}
|
|
1810
|
+
deployOps += deep.unsolicitedActions.filter((a) => a.action === "deploy").length;
|
|
1811
|
+
deleteOps += deep.unsolicitedActions.filter((a) => a.action === "delete").length;
|
|
1812
|
+
}
|
|
1813
|
+
evidence.push({ icon: "missing", text: "No approval routing \u2014 critical actions not routed for human review" });
|
|
1814
|
+
evidence.push({ icon: "missing", text: "No raw intent routing \u2014 humans may see agent-reframed summaries" });
|
|
1815
|
+
evidence.push({ icon: "missing", text: "No policy-generated explanations \u2014 agents frame their own requests" });
|
|
1816
|
+
evidence.push({ icon: "missing", text: "No protection against approval fatigue" });
|
|
1817
|
+
if (shellCalls === 0) {
|
|
1818
|
+
return {
|
|
1819
|
+
code,
|
|
1820
|
+
title,
|
|
1821
|
+
status: "PARTIAL",
|
|
1822
|
+
evidence,
|
|
1823
|
+
summary: "No critical actions in logs, but no approval workflow exists.",
|
|
1824
|
+
details: "No file writes, deletions, or deployments detected. But no human approval workflow exists \u2014 when critical actions occur, they will execute without review.",
|
|
1825
|
+
recommendation: "Implement approval routing for critical actions. Add raw intent display."
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
if (deployOps === 0 && deleteOps === 0) {
|
|
1829
|
+
return {
|
|
1830
|
+
code,
|
|
1831
|
+
title,
|
|
1832
|
+
status: "PARTIAL",
|
|
1833
|
+
evidence,
|
|
1834
|
+
summary: `${shellCalls} shell calls without approval routing, but no high-impact actions.`,
|
|
1835
|
+
details: "Shell commands executed without human approval workflow. No deployments or destructive deletions detected. But no approval routing exists for when high-impact actions occur.",
|
|
1836
|
+
recommendation: "Implement approval routing. Add raw intent routing. Ensure policy-generated explanations."
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
const impactRate = shellCalls > 0 ? ((deployOps + deleteOps) / shellCalls * 100).toFixed(1) : "0";
|
|
1840
|
+
if ((deployOps + deleteOps) / (shellCalls || 1) < 0.1) {
|
|
1841
|
+
return {
|
|
1842
|
+
code,
|
|
1843
|
+
title,
|
|
1844
|
+
status: "PARTIAL",
|
|
1845
|
+
evidence,
|
|
1846
|
+
summary: `${deployOps + deleteOps} high-impact action(s) in ${shellCalls} shell calls (${impactRate}%).`,
|
|
1847
|
+
details: "Some deploy/delete operations without approval, but at low frequency relative to total shell usage.",
|
|
1848
|
+
recommendation: "Implement approval routing for critical actions. Add raw intent display."
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
return {
|
|
1852
|
+
code,
|
|
1853
|
+
title,
|
|
1854
|
+
status: "NOT_PROTECTED",
|
|
1855
|
+
evidence,
|
|
1856
|
+
summary: `${deployOps + deleteOps} high-impact action(s) in ${shellCalls} shell calls (${impactRate}%).`,
|
|
1857
|
+
details: "Deployments, deletions, or publishes executed without human review. No approval routing, no raw intent display, no policy-generated explanations.",
|
|
1858
|
+
recommendation: "Implement approval routing for critical actions. Add raw intent display. Ensure policy-generated explanations."
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// src/audit/checks/asi10-rogue-agents.ts
|
|
1863
|
+
function checkRogueAgents(data, deep) {
|
|
1864
|
+
const code = "ASI10";
|
|
1865
|
+
const title = "Rogue Agents";
|
|
1866
|
+
const evidence = [];
|
|
1867
|
+
let scopeEscalation = 0;
|
|
1868
|
+
let volumeSpikes = 0;
|
|
1869
|
+
let unusualTools = 0;
|
|
1870
|
+
const avgCallsPerSession = data.sessions.length > 0 ? data.totalToolCalls / data.sessions.length : 0;
|
|
1871
|
+
for (const session of data.sessions) {
|
|
1872
|
+
if (session.toolCalls.length > avgCallsPerSession * 3 && avgCallsPerSession > 10) {
|
|
1873
|
+
volumeSpikes++;
|
|
1874
|
+
}
|
|
1875
|
+
const calls = session.toolCalls;
|
|
1876
|
+
const firstHalf = calls.slice(0, Math.floor(calls.length / 2));
|
|
1877
|
+
const secondHalf = calls.slice(Math.floor(calls.length / 2));
|
|
1878
|
+
const firstDangerous = firstHalf.filter((c) => {
|
|
1879
|
+
const t = c.toolName.toLowerCase();
|
|
1880
|
+
return t.includes("exec") || t.includes("bash") || t.includes("shell") || t.includes("delete");
|
|
1881
|
+
}).length;
|
|
1882
|
+
const secondDangerous = secondHalf.filter((c) => {
|
|
1883
|
+
const t = c.toolName.toLowerCase();
|
|
1884
|
+
return t.includes("exec") || t.includes("bash") || t.includes("shell") || t.includes("delete");
|
|
1885
|
+
}).length;
|
|
1886
|
+
if (secondHalf.length > 5 && secondDangerous > firstDangerous * 2 && secondDangerous > 3) {
|
|
1887
|
+
scopeEscalation++;
|
|
1888
|
+
}
|
|
1889
|
+
const toolSet = new Set(calls.map((c) => c.toolName));
|
|
1890
|
+
for (const tool of toolSet) {
|
|
1891
|
+
const otherSessions = data.sessions.filter((s) => s.id !== session.id);
|
|
1892
|
+
const usedElsewhere = otherSessions.some((s) => s.toolCalls.some((tc) => tc.toolName === tool));
|
|
1893
|
+
if (!usedElsewhere && calls.filter((c) => c.toolName === tool).length > 5) {
|
|
1894
|
+
unusualTools++;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
evidence.push({ icon: "info", text: `${data.sessions.length} session(s), avg ${Math.round(avgCallsPerSession)} calls/session` });
|
|
1899
|
+
if (volumeSpikes > 0) {
|
|
1900
|
+
evidence.push({ icon: "warn", text: `${volumeSpikes} session(s) with 3x+ volume spike (anomalous activity)` });
|
|
1901
|
+
}
|
|
1902
|
+
if (scopeEscalation > 0) {
|
|
1903
|
+
evidence.push({ icon: "warn", text: `${scopeEscalation} session(s) show scope escalation (reads \u2192 exec/delete)` });
|
|
1904
|
+
}
|
|
1905
|
+
if (unusualTools > 0) {
|
|
1906
|
+
evidence.push({ icon: "warn", text: `${unusualTools} unusual tool usage pattern(s) (tools only used in one session)` });
|
|
1907
|
+
}
|
|
1908
|
+
if (deep) {
|
|
1909
|
+
if (deep.permissionDrifts.length > 0) {
|
|
1910
|
+
scopeEscalation = Math.max(scopeEscalation, deep.permissionDrifts.length);
|
|
1911
|
+
for (const drift of deep.permissionDrifts.slice(0, 3)) {
|
|
1912
|
+
evidence.push({
|
|
1913
|
+
icon: "warn",
|
|
1914
|
+
text: `Session ${drift.sessionId.slice(0, 8)}: privilege drift ${drift.driftRatio.toFixed(1)}x (level ${drift.earlyPrivilegeLevel.toFixed(1)} \u2192 ${drift.latePrivilegeLevel.toFixed(1)})` + (drift.newToolTypes.length > 0 ? ` | new: ${drift.newToolTypes.join(", ")}` : "")
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (deep.anomalies.length > 0) {
|
|
1919
|
+
volumeSpikes = Math.max(volumeSpikes, deep.anomalies.length);
|
|
1920
|
+
evidence.push({ icon: "warn", text: `${deep.anomalies.length} session(s) deviate >2 stddev from behavioral baseline` });
|
|
1921
|
+
for (const a of deep.anomalies.slice(0, 3)) {
|
|
1922
|
+
evidence.push({ icon: "warn", text: ` ${a.sessionId.slice(0, 8)}: ${a.deviations[0]}` });
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
const allBaseline = deep.baselines.find((b) => b.source === "all");
|
|
1926
|
+
if (allBaseline) {
|
|
1927
|
+
const dist = Object.entries(allBaseline.toolTypeDistribution).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v.toFixed(0)}%`).join(", ");
|
|
1928
|
+
evidence.push({ icon: "info", text: `Baseline: avg ${Math.round(allBaseline.avgToolCalls)} calls/session | ${dist}` });
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
evidence.push({ icon: "missing", text: "No remote kill switch \u2014 cannot instantly stop a rogue agent" });
|
|
1932
|
+
evidence.push({ icon: "missing", text: "No auto-shutdown on anomalous behavior" });
|
|
1933
|
+
evidence.push({ icon: "missing", text: "No scope boundaries \u2014 agents can self-escalate" });
|
|
1934
|
+
const totalAnomalies = volumeSpikes + scopeEscalation;
|
|
1935
|
+
const sessionCount = data.sessions.length || 1;
|
|
1936
|
+
const anomalyRate = (totalAnomalies / sessionCount * 100).toFixed(0);
|
|
1937
|
+
if (totalAnomalies > 0 && totalAnomalies / sessionCount < 0.2) {
|
|
1938
|
+
return {
|
|
1939
|
+
code,
|
|
1940
|
+
title,
|
|
1941
|
+
status: "PARTIAL",
|
|
1942
|
+
evidence,
|
|
1943
|
+
summary: `${totalAnomalies} anomaly(ies) in ${sessionCount} sessions (${anomalyRate}%) \u2014 no kill switch.`,
|
|
1944
|
+
details: "Some anomalous patterns at low frequency. No CUSUM baselines or kill switch, but current risk is moderate.",
|
|
1945
|
+
recommendation: "Implement CUSUM behavioral baselines. Add remote kill switch. Add auto-shutdown on anomaly."
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
if (totalAnomalies > 0) {
|
|
1949
|
+
return {
|
|
1950
|
+
code,
|
|
1951
|
+
title,
|
|
1952
|
+
status: "NOT_PROTECTED",
|
|
1953
|
+
evidence,
|
|
1954
|
+
summary: `${totalAnomalies} anomaly(ies) in ${sessionCount} sessions (${anomalyRate}%) \u2014 no kill switch.`,
|
|
1955
|
+
details: "Volume spikes or scope escalation patterns found in many sessions. No CUSUM baselines to detect drift. No remote kill switch or auto-shutdown.",
|
|
1956
|
+
recommendation: "Implement CUSUM behavioral baselines. Add remote kill switch. Add auto-shutdown on anomaly."
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
code,
|
|
1961
|
+
title,
|
|
1962
|
+
status: "NOT_PROTECTED",
|
|
1963
|
+
evidence,
|
|
1964
|
+
summary: "No behavioral monitoring or kill switch. Rogue agents undetectable.",
|
|
1965
|
+
details: "No behavioral baselines, no drift detection, no kill switch. If an agent becomes rogue, there is no way to detect or stop it.",
|
|
1966
|
+
recommendation: "Implement CUSUM behavioral baselines. Add remote kill switch. Add auto-shutdown. Set scope boundaries."
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// src/audit/checks/index.ts
|
|
1971
|
+
function runAllChecks(data) {
|
|
1972
|
+
const { baselines, anomalies, permissionDrifts } = analyzeBaseline(data.sessions);
|
|
1973
|
+
const deep = {
|
|
1974
|
+
chains: analyzeChains(data.sessions),
|
|
1975
|
+
dataFlowLeaks: analyzeDataFlow(data.sessions),
|
|
1976
|
+
permissionDrifts,
|
|
1977
|
+
baselines,
|
|
1978
|
+
anomalies,
|
|
1979
|
+
unsolicitedActions: findUnsolicitedActions(data.sessions)
|
|
1980
|
+
};
|
|
1981
|
+
return [
|
|
1982
|
+
checkGoalHijacking(data, deep),
|
|
1983
|
+
checkToolMisuse(data, deep),
|
|
1984
|
+
checkIdentityAbuse(data, deep),
|
|
1985
|
+
checkSupplyChain(data),
|
|
1986
|
+
checkCodeExecution(data, deep),
|
|
1987
|
+
checkMemoryPoisoning(data),
|
|
1988
|
+
checkInterAgent(data),
|
|
1989
|
+
checkCascadingFailures(data, deep),
|
|
1990
|
+
checkHumanTrust(data, deep),
|
|
1991
|
+
checkRogueAgents(data, deep)
|
|
1992
|
+
];
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
1996
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
1997
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
1998
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
1999
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
2000
|
+
var styles = {
|
|
2001
|
+
modifier: {
|
|
2002
|
+
reset: [0, 0],
|
|
2003
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
2004
|
+
bold: [1, 22],
|
|
2005
|
+
dim: [2, 22],
|
|
2006
|
+
italic: [3, 23],
|
|
2007
|
+
underline: [4, 24],
|
|
2008
|
+
overline: [53, 55],
|
|
2009
|
+
inverse: [7, 27],
|
|
2010
|
+
hidden: [8, 28],
|
|
2011
|
+
strikethrough: [9, 29]
|
|
2012
|
+
},
|
|
2013
|
+
color: {
|
|
2014
|
+
black: [30, 39],
|
|
2015
|
+
red: [31, 39],
|
|
2016
|
+
green: [32, 39],
|
|
2017
|
+
yellow: [33, 39],
|
|
2018
|
+
blue: [34, 39],
|
|
2019
|
+
magenta: [35, 39],
|
|
2020
|
+
cyan: [36, 39],
|
|
2021
|
+
white: [37, 39],
|
|
2022
|
+
// Bright color
|
|
2023
|
+
blackBright: [90, 39],
|
|
2024
|
+
gray: [90, 39],
|
|
2025
|
+
// Alias of `blackBright`
|
|
2026
|
+
grey: [90, 39],
|
|
2027
|
+
// Alias of `blackBright`
|
|
2028
|
+
redBright: [91, 39],
|
|
2029
|
+
greenBright: [92, 39],
|
|
2030
|
+
yellowBright: [93, 39],
|
|
2031
|
+
blueBright: [94, 39],
|
|
2032
|
+
magentaBright: [95, 39],
|
|
2033
|
+
cyanBright: [96, 39],
|
|
2034
|
+
whiteBright: [97, 39]
|
|
2035
|
+
},
|
|
2036
|
+
bgColor: {
|
|
2037
|
+
bgBlack: [40, 49],
|
|
2038
|
+
bgRed: [41, 49],
|
|
2039
|
+
bgGreen: [42, 49],
|
|
2040
|
+
bgYellow: [43, 49],
|
|
2041
|
+
bgBlue: [44, 49],
|
|
2042
|
+
bgMagenta: [45, 49],
|
|
2043
|
+
bgCyan: [46, 49],
|
|
2044
|
+
bgWhite: [47, 49],
|
|
2045
|
+
// Bright color
|
|
2046
|
+
bgBlackBright: [100, 49],
|
|
2047
|
+
bgGray: [100, 49],
|
|
2048
|
+
// Alias of `bgBlackBright`
|
|
2049
|
+
bgGrey: [100, 49],
|
|
2050
|
+
// Alias of `bgBlackBright`
|
|
2051
|
+
bgRedBright: [101, 49],
|
|
2052
|
+
bgGreenBright: [102, 49],
|
|
2053
|
+
bgYellowBright: [103, 49],
|
|
2054
|
+
bgBlueBright: [104, 49],
|
|
2055
|
+
bgMagentaBright: [105, 49],
|
|
2056
|
+
bgCyanBright: [106, 49],
|
|
2057
|
+
bgWhiteBright: [107, 49]
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
2061
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
2062
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
2063
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
2064
|
+
function assembleStyles() {
|
|
2065
|
+
const codes = /* @__PURE__ */ new Map();
|
|
2066
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
2067
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
2068
|
+
styles[styleName] = {
|
|
2069
|
+
open: `\x1B[${style[0]}m`,
|
|
2070
|
+
close: `\x1B[${style[1]}m`
|
|
2071
|
+
};
|
|
2072
|
+
group[styleName] = styles[styleName];
|
|
2073
|
+
codes.set(style[0], style[1]);
|
|
2074
|
+
}
|
|
2075
|
+
Object.defineProperty(styles, groupName, {
|
|
2076
|
+
value: group,
|
|
2077
|
+
enumerable: false
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
Object.defineProperty(styles, "codes", {
|
|
2081
|
+
value: codes,
|
|
2082
|
+
enumerable: false
|
|
2083
|
+
});
|
|
2084
|
+
styles.color.close = "\x1B[39m";
|
|
2085
|
+
styles.bgColor.close = "\x1B[49m";
|
|
2086
|
+
styles.color.ansi = wrapAnsi16();
|
|
2087
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
2088
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
2089
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
2090
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
2091
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
2092
|
+
Object.defineProperties(styles, {
|
|
2093
|
+
rgbToAnsi256: {
|
|
2094
|
+
value(red, green, blue) {
|
|
2095
|
+
if (red === green && green === blue) {
|
|
2096
|
+
if (red < 8) {
|
|
2097
|
+
return 16;
|
|
2098
|
+
}
|
|
2099
|
+
if (red > 248) {
|
|
2100
|
+
return 231;
|
|
2101
|
+
}
|
|
2102
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
2103
|
+
}
|
|
2104
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
2105
|
+
},
|
|
2106
|
+
enumerable: false
|
|
2107
|
+
},
|
|
2108
|
+
hexToRgb: {
|
|
2109
|
+
value(hex) {
|
|
2110
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
2111
|
+
if (!matches) {
|
|
2112
|
+
return [0, 0, 0];
|
|
2113
|
+
}
|
|
2114
|
+
let [colorString] = matches;
|
|
2115
|
+
if (colorString.length === 3) {
|
|
2116
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
2117
|
+
}
|
|
2118
|
+
const integer = Number.parseInt(colorString, 16);
|
|
2119
|
+
return [
|
|
2120
|
+
/* eslint-disable no-bitwise */
|
|
2121
|
+
integer >> 16 & 255,
|
|
2122
|
+
integer >> 8 & 255,
|
|
2123
|
+
integer & 255
|
|
2124
|
+
/* eslint-enable no-bitwise */
|
|
2125
|
+
];
|
|
2126
|
+
},
|
|
2127
|
+
enumerable: false
|
|
2128
|
+
},
|
|
2129
|
+
hexToAnsi256: {
|
|
2130
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
2131
|
+
enumerable: false
|
|
2132
|
+
},
|
|
2133
|
+
ansi256ToAnsi: {
|
|
2134
|
+
value(code) {
|
|
2135
|
+
if (code < 8) {
|
|
2136
|
+
return 30 + code;
|
|
2137
|
+
}
|
|
2138
|
+
if (code < 16) {
|
|
2139
|
+
return 90 + (code - 8);
|
|
2140
|
+
}
|
|
2141
|
+
let red;
|
|
2142
|
+
let green;
|
|
2143
|
+
let blue;
|
|
2144
|
+
if (code >= 232) {
|
|
2145
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
2146
|
+
green = red;
|
|
2147
|
+
blue = red;
|
|
2148
|
+
} else {
|
|
2149
|
+
code -= 16;
|
|
2150
|
+
const remainder = code % 36;
|
|
2151
|
+
red = Math.floor(code / 36) / 5;
|
|
2152
|
+
green = Math.floor(remainder / 6) / 5;
|
|
2153
|
+
blue = remainder % 6 / 5;
|
|
2154
|
+
}
|
|
2155
|
+
const value = Math.max(red, green, blue) * 2;
|
|
2156
|
+
if (value === 0) {
|
|
2157
|
+
return 30;
|
|
2158
|
+
}
|
|
2159
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
2160
|
+
if (value === 2) {
|
|
2161
|
+
result += 60;
|
|
2162
|
+
}
|
|
2163
|
+
return result;
|
|
2164
|
+
},
|
|
2165
|
+
enumerable: false
|
|
2166
|
+
},
|
|
2167
|
+
rgbToAnsi: {
|
|
2168
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
2169
|
+
enumerable: false
|
|
2170
|
+
},
|
|
2171
|
+
hexToAnsi: {
|
|
2172
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
2173
|
+
enumerable: false
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
return styles;
|
|
2177
|
+
}
|
|
2178
|
+
var ansiStyles = assembleStyles();
|
|
2179
|
+
var ansi_styles_default = ansiStyles;
|
|
2180
|
+
|
|
2181
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
|
|
2182
|
+
import process2 from "process";
|
|
2183
|
+
import os from "os";
|
|
2184
|
+
import tty from "tty";
|
|
2185
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
2186
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
2187
|
+
const position = argv.indexOf(prefix + flag);
|
|
2188
|
+
const terminatorPosition = argv.indexOf("--");
|
|
2189
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
2190
|
+
}
|
|
2191
|
+
var { env } = process2;
|
|
2192
|
+
var flagForceColor;
|
|
2193
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
2194
|
+
flagForceColor = 0;
|
|
2195
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
2196
|
+
flagForceColor = 1;
|
|
2197
|
+
}
|
|
2198
|
+
function envForceColor() {
|
|
2199
|
+
if ("FORCE_COLOR" in env) {
|
|
2200
|
+
if (env.FORCE_COLOR === "true") {
|
|
2201
|
+
return 1;
|
|
2202
|
+
}
|
|
2203
|
+
if (env.FORCE_COLOR === "false") {
|
|
2204
|
+
return 0;
|
|
2205
|
+
}
|
|
2206
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
function translateLevel(level) {
|
|
2210
|
+
if (level === 0) {
|
|
2211
|
+
return false;
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
level,
|
|
2215
|
+
hasBasic: true,
|
|
2216
|
+
has256: level >= 2,
|
|
2217
|
+
has16m: level >= 3
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
2221
|
+
const noFlagForceColor = envForceColor();
|
|
2222
|
+
if (noFlagForceColor !== void 0) {
|
|
2223
|
+
flagForceColor = noFlagForceColor;
|
|
2224
|
+
}
|
|
2225
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
2226
|
+
if (forceColor === 0) {
|
|
2227
|
+
return 0;
|
|
2228
|
+
}
|
|
2229
|
+
if (sniffFlags) {
|
|
2230
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
2231
|
+
return 3;
|
|
2232
|
+
}
|
|
2233
|
+
if (hasFlag("color=256")) {
|
|
2234
|
+
return 2;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
2238
|
+
return 1;
|
|
2239
|
+
}
|
|
2240
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
2241
|
+
return 0;
|
|
2242
|
+
}
|
|
2243
|
+
const min = forceColor || 0;
|
|
2244
|
+
if (env.TERM === "dumb") {
|
|
2245
|
+
return min;
|
|
2246
|
+
}
|
|
2247
|
+
if (process2.platform === "win32") {
|
|
2248
|
+
const osRelease = os.release().split(".");
|
|
2249
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
2250
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
2251
|
+
}
|
|
2252
|
+
return 1;
|
|
2253
|
+
}
|
|
2254
|
+
if ("CI" in env) {
|
|
2255
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
2256
|
+
return 3;
|
|
2257
|
+
}
|
|
2258
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
2259
|
+
return 1;
|
|
2260
|
+
}
|
|
2261
|
+
return min;
|
|
2262
|
+
}
|
|
2263
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
2264
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
2265
|
+
}
|
|
2266
|
+
if (env.COLORTERM === "truecolor") {
|
|
2267
|
+
return 3;
|
|
2268
|
+
}
|
|
2269
|
+
if (env.TERM === "xterm-kitty") {
|
|
2270
|
+
return 3;
|
|
2271
|
+
}
|
|
2272
|
+
if (env.TERM === "xterm-ghostty") {
|
|
2273
|
+
return 3;
|
|
2274
|
+
}
|
|
2275
|
+
if (env.TERM === "wezterm") {
|
|
2276
|
+
return 3;
|
|
2277
|
+
}
|
|
2278
|
+
if ("TERM_PROGRAM" in env) {
|
|
2279
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
2280
|
+
switch (env.TERM_PROGRAM) {
|
|
2281
|
+
case "iTerm.app": {
|
|
2282
|
+
return version >= 3 ? 3 : 2;
|
|
2283
|
+
}
|
|
2284
|
+
case "Apple_Terminal": {
|
|
2285
|
+
return 2;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
2290
|
+
return 2;
|
|
2291
|
+
}
|
|
2292
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
2293
|
+
return 1;
|
|
2294
|
+
}
|
|
2295
|
+
if ("COLORTERM" in env) {
|
|
2296
|
+
return 1;
|
|
2297
|
+
}
|
|
2298
|
+
return min;
|
|
2299
|
+
}
|
|
2300
|
+
function createSupportsColor(stream, options = {}) {
|
|
2301
|
+
const level = _supportsColor(stream, {
|
|
2302
|
+
streamIsTTY: stream && stream.isTTY,
|
|
2303
|
+
...options
|
|
2304
|
+
});
|
|
2305
|
+
return translateLevel(level);
|
|
2306
|
+
}
|
|
2307
|
+
var supportsColor = {
|
|
2308
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
2309
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
2310
|
+
};
|
|
2311
|
+
var supports_color_default = supportsColor;
|
|
2312
|
+
|
|
2313
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/utilities.js
|
|
2314
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
2315
|
+
let index = string.indexOf(substring);
|
|
2316
|
+
if (index === -1) {
|
|
2317
|
+
return string;
|
|
2318
|
+
}
|
|
2319
|
+
const substringLength = substring.length;
|
|
2320
|
+
let endIndex = 0;
|
|
2321
|
+
let returnValue = "";
|
|
2322
|
+
do {
|
|
2323
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
2324
|
+
endIndex = index + substringLength;
|
|
2325
|
+
index = string.indexOf(substring, endIndex);
|
|
2326
|
+
} while (index !== -1);
|
|
2327
|
+
returnValue += string.slice(endIndex);
|
|
2328
|
+
return returnValue;
|
|
2329
|
+
}
|
|
2330
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
2331
|
+
let endIndex = 0;
|
|
2332
|
+
let returnValue = "";
|
|
2333
|
+
do {
|
|
2334
|
+
const gotCR = string[index - 1] === "\r";
|
|
2335
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
2336
|
+
endIndex = index + 1;
|
|
2337
|
+
index = string.indexOf("\n", endIndex);
|
|
2338
|
+
} while (index !== -1);
|
|
2339
|
+
returnValue += string.slice(endIndex);
|
|
2340
|
+
return returnValue;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
|
|
2344
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
2345
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
2346
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
2347
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
2348
|
+
var levelMapping = [
|
|
2349
|
+
"ansi",
|
|
2350
|
+
"ansi",
|
|
2351
|
+
"ansi256",
|
|
2352
|
+
"ansi16m"
|
|
2353
|
+
];
|
|
2354
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
2355
|
+
var applyOptions = (object, options = {}) => {
|
|
2356
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
2357
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
2358
|
+
}
|
|
2359
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
2360
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
2361
|
+
};
|
|
2362
|
+
var chalkFactory = (options) => {
|
|
2363
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
2364
|
+
applyOptions(chalk2, options);
|
|
2365
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
2366
|
+
return chalk2;
|
|
2367
|
+
};
|
|
2368
|
+
function createChalk(options) {
|
|
2369
|
+
return chalkFactory(options);
|
|
2370
|
+
}
|
|
2371
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
2372
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
2373
|
+
styles2[styleName] = {
|
|
2374
|
+
get() {
|
|
2375
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
2376
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
2377
|
+
return builder;
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
styles2.visible = {
|
|
2382
|
+
get() {
|
|
2383
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
2384
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
2385
|
+
return builder;
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
2389
|
+
if (model === "rgb") {
|
|
2390
|
+
if (level === "ansi16m") {
|
|
2391
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
2392
|
+
}
|
|
2393
|
+
if (level === "ansi256") {
|
|
2394
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
2395
|
+
}
|
|
2396
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
2397
|
+
}
|
|
2398
|
+
if (model === "hex") {
|
|
2399
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
2400
|
+
}
|
|
2401
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
2402
|
+
};
|
|
2403
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
2404
|
+
for (const model of usedModels) {
|
|
2405
|
+
styles2[model] = {
|
|
2406
|
+
get() {
|
|
2407
|
+
const { level } = this;
|
|
2408
|
+
return function(...arguments_) {
|
|
2409
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
2410
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
2415
|
+
styles2[bgModel] = {
|
|
2416
|
+
get() {
|
|
2417
|
+
const { level } = this;
|
|
2418
|
+
return function(...arguments_) {
|
|
2419
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
2420
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
var proto = Object.defineProperties(() => {
|
|
2426
|
+
}, {
|
|
2427
|
+
...styles2,
|
|
2428
|
+
level: {
|
|
2429
|
+
enumerable: true,
|
|
2430
|
+
get() {
|
|
2431
|
+
return this[GENERATOR].level;
|
|
2432
|
+
},
|
|
2433
|
+
set(level) {
|
|
2434
|
+
this[GENERATOR].level = level;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
var createStyler = (open, close, parent) => {
|
|
2439
|
+
let openAll;
|
|
2440
|
+
let closeAll;
|
|
2441
|
+
if (parent === void 0) {
|
|
2442
|
+
openAll = open;
|
|
2443
|
+
closeAll = close;
|
|
2444
|
+
} else {
|
|
2445
|
+
openAll = parent.openAll + open;
|
|
2446
|
+
closeAll = close + parent.closeAll;
|
|
2447
|
+
}
|
|
2448
|
+
return {
|
|
2449
|
+
open,
|
|
2450
|
+
close,
|
|
2451
|
+
openAll,
|
|
2452
|
+
closeAll,
|
|
2453
|
+
parent
|
|
2454
|
+
};
|
|
2455
|
+
};
|
|
2456
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
2457
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
2458
|
+
Object.setPrototypeOf(builder, proto);
|
|
2459
|
+
builder[GENERATOR] = self;
|
|
2460
|
+
builder[STYLER] = _styler;
|
|
2461
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
2462
|
+
return builder;
|
|
2463
|
+
};
|
|
2464
|
+
var applyStyle = (self, string) => {
|
|
2465
|
+
if (self.level <= 0 || !string) {
|
|
2466
|
+
return self[IS_EMPTY] ? "" : string;
|
|
2467
|
+
}
|
|
2468
|
+
let styler = self[STYLER];
|
|
2469
|
+
if (styler === void 0) {
|
|
2470
|
+
return string;
|
|
2471
|
+
}
|
|
2472
|
+
const { openAll, closeAll } = styler;
|
|
2473
|
+
if (string.includes("\x1B")) {
|
|
2474
|
+
while (styler !== void 0) {
|
|
2475
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
2476
|
+
styler = styler.parent;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const lfIndex = string.indexOf("\n");
|
|
2480
|
+
if (lfIndex !== -1) {
|
|
2481
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
2482
|
+
}
|
|
2483
|
+
return openAll + string + closeAll;
|
|
2484
|
+
};
|
|
2485
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
2486
|
+
var chalk = createChalk();
|
|
2487
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
2488
|
+
var source_default = chalk;
|
|
2489
|
+
|
|
2490
|
+
// src/audit/reporter.ts
|
|
2491
|
+
var STATUS_ICON = {
|
|
2492
|
+
PROTECTED: source_default.green("\u2705"),
|
|
2493
|
+
PARTIAL: source_default.yellow("\u26A0\uFE0F"),
|
|
2494
|
+
NOT_PROTECTED: source_default.red("\u274C")
|
|
2495
|
+
};
|
|
2496
|
+
var STATUS_LABEL = {
|
|
2497
|
+
PROTECTED: source_default.green.bold("PROTECTED"),
|
|
2498
|
+
PARTIAL: source_default.yellow.bold("PARTIAL"),
|
|
2499
|
+
NOT_PROTECTED: source_default.red.bold("NOT PROTECTED")
|
|
2500
|
+
};
|
|
2501
|
+
var EV_ICON = {
|
|
2502
|
+
found: source_default.green("\u2022"),
|
|
2503
|
+
missing: source_default.red("\u2022"),
|
|
2504
|
+
warn: source_default.yellow("\u2022"),
|
|
2505
|
+
info: source_default.dim("\u2022")
|
|
2506
|
+
};
|
|
2507
|
+
function printHeader() {
|
|
2508
|
+
console.log("");
|
|
2509
|
+
const title = " SolonGate Security Audit \u2014 OWASP Agentic Top 10 ";
|
|
2510
|
+
const border = "\u2500".repeat(title.length);
|
|
2511
|
+
console.log(source_default.bold.white("\u250C" + border + "\u2510"));
|
|
2512
|
+
console.log(source_default.bold.white("\u2502") + title + source_default.bold.white("\u2502"));
|
|
2513
|
+
console.log(source_default.bold.white("\u2514" + border + "\u2518"));
|
|
2514
|
+
console.log("");
|
|
2515
|
+
}
|
|
2516
|
+
function printLogSummary(data) {
|
|
2517
|
+
if (data.sources.length > 0) {
|
|
2518
|
+
console.log(source_default.dim(" AI Tools: ") + data.sources.join(", "));
|
|
2519
|
+
} else {
|
|
2520
|
+
console.log(source_default.dim(" AI Tools: ") + source_default.red("No AI tool logs found"));
|
|
2521
|
+
}
|
|
2522
|
+
console.log(source_default.dim(" Sessions: ") + `${data.sessions.length} total, ${data.totalToolCalls} tool calls`);
|
|
2523
|
+
if (data.timeRange) {
|
|
2524
|
+
const from = new Date(data.timeRange.from).toLocaleDateString();
|
|
2525
|
+
const to = new Date(data.timeRange.to).toLocaleDateString();
|
|
2526
|
+
console.log(source_default.dim(" Period: ") + `${from} \u2014 ${to}`);
|
|
2527
|
+
}
|
|
2528
|
+
console.log("");
|
|
2529
|
+
}
|
|
2530
|
+
function calcScore(results) {
|
|
2531
|
+
let score = 0, fixCount = 0;
|
|
2532
|
+
for (const r of results) {
|
|
2533
|
+
if (r.status === "PROTECTED") score += 1;
|
|
2534
|
+
else if (r.status === "PARTIAL") score += 0.5;
|
|
2535
|
+
if (r.status === "NOT_PROTECTED") fixCount++;
|
|
2536
|
+
}
|
|
2537
|
+
return { intScore: Math.floor(score), fixCount };
|
|
2538
|
+
}
|
|
2539
|
+
function printCompactReport(results) {
|
|
2540
|
+
const sorted = [...results].sort((a, b) => {
|
|
2541
|
+
const order = { PROTECTED: 0, PARTIAL: 1, NOT_PROTECTED: 2 };
|
|
2542
|
+
return order[a.status] - order[b.status];
|
|
2543
|
+
});
|
|
2544
|
+
for (const r of sorted) {
|
|
2545
|
+
console.log(`${STATUS_ICON[r.status]} ${`${r.code} ${r.title}`.padEnd(24)} ${STATUS_LABEL[r.status]}`);
|
|
2546
|
+
console.log(source_default.dim(` ${r.summary}`));
|
|
2547
|
+
}
|
|
2548
|
+
console.log("");
|
|
2549
|
+
}
|
|
2550
|
+
function printScore(results) {
|
|
2551
|
+
const { intScore, fixCount } = calcScore(results);
|
|
2552
|
+
console.log(` Security Score: ${(intScore >= 7 ? source_default.green : intScore >= 4 ? source_default.yellow : source_default.red).bold(`${intScore}/10`)}`);
|
|
2553
|
+
console.log("");
|
|
2554
|
+
if (fixCount > 0) {
|
|
2555
|
+
console.log(` Fix ${fixCount} critical issue${fixCount > 1 ? "s" : ""} \u2192 ${source_default.cyan.underline("solongate.com")}`);
|
|
2556
|
+
} else if (intScore < 10) {
|
|
2557
|
+
console.log(source_default.yellow.dim(" No critical gaps, but improvements possible."));
|
|
2558
|
+
} else {
|
|
2559
|
+
console.log(source_default.green.bold(" Maximum protection achieved!"));
|
|
2560
|
+
}
|
|
2561
|
+
console.log("");
|
|
2562
|
+
}
|
|
2563
|
+
function printDetailedReport(results) {
|
|
2564
|
+
const sorted = [...results].sort((a, b) => {
|
|
2565
|
+
const order = { PROTECTED: 0, PARTIAL: 1, NOT_PROTECTED: 2 };
|
|
2566
|
+
return order[a.status] - order[b.status];
|
|
2567
|
+
});
|
|
2568
|
+
console.log(source_default.bold("\u2500".repeat(56)));
|
|
2569
|
+
console.log(source_default.bold(" DETAILED ANALYSIS"));
|
|
2570
|
+
console.log(source_default.bold("\u2500".repeat(56)));
|
|
2571
|
+
console.log("");
|
|
2572
|
+
for (const r of sorted) {
|
|
2573
|
+
console.log(` ${STATUS_ICON[r.status]} ${source_default.bold(`${r.code} ${r.title}`)} ${STATUS_LABEL[r.status]}`);
|
|
2574
|
+
console.log(` ${r.summary}`);
|
|
2575
|
+
console.log("");
|
|
2576
|
+
for (const e of r.evidence) {
|
|
2577
|
+
const lines = e.text.split("\n");
|
|
2578
|
+
console.log(` ${EV_ICON[e.icon]} ${lines[0]}`);
|
|
2579
|
+
for (let i = 1; i < lines.length; i++) console.log(` ${lines[i]}`);
|
|
2580
|
+
}
|
|
2581
|
+
console.log("");
|
|
2582
|
+
console.log(` ${source_default.dim(r.details)}`);
|
|
2583
|
+
if (r.recommendation) {
|
|
2584
|
+
console.log("");
|
|
2585
|
+
console.log(` ${source_default.cyan("\u279C")} ${source_default.cyan(r.recommendation)}`);
|
|
2586
|
+
}
|
|
2587
|
+
console.log("");
|
|
2588
|
+
console.log(source_default.dim(" " + "\u2500".repeat(46)));
|
|
2589
|
+
console.log("");
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
function printFooter(results) {
|
|
2593
|
+
const { intScore, fixCount } = calcScore(results);
|
|
2594
|
+
console.log(` Security Score: ${intScore}/10` + (fixCount > 0 ? " \u2014 Critical risks detected." : ""));
|
|
2595
|
+
if (fixCount > 0) {
|
|
2596
|
+
console.log(` Run ${source_default.cyan("npx solongate")} to fix \u2192 ${source_default.cyan.underline("solongate.com")}`);
|
|
2597
|
+
}
|
|
2598
|
+
console.log("");
|
|
2599
|
+
}
|
|
2600
|
+
var SOURCE_COLOR = {
|
|
2601
|
+
claude: source_default.magenta,
|
|
2602
|
+
gemini: source_default.blue,
|
|
2603
|
+
openclaw: source_default.green
|
|
2604
|
+
};
|
|
2605
|
+
var SOURCE_LABEL = {
|
|
2606
|
+
claude: "Claude",
|
|
2607
|
+
gemini: "Gemini",
|
|
2608
|
+
openclaw: "OClaw"
|
|
2609
|
+
};
|
|
2610
|
+
function truncate(s, max) {
|
|
2611
|
+
if (s.length <= max) return s;
|
|
2612
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
2613
|
+
}
|
|
2614
|
+
function formatTime(ts) {
|
|
2615
|
+
if (!ts) return " ";
|
|
2616
|
+
try {
|
|
2617
|
+
const d = new Date(ts);
|
|
2618
|
+
return d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
2619
|
+
} catch {
|
|
2620
|
+
return ts.slice(11, 19);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function formatDate(ts) {
|
|
2624
|
+
if (!ts) return "";
|
|
2625
|
+
try {
|
|
2626
|
+
const d = new Date(ts);
|
|
2627
|
+
return d.toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit" });
|
|
2628
|
+
} catch {
|
|
2629
|
+
return "";
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function extractArgSummary(args2) {
|
|
2633
|
+
if (args2.command) return String(args2.command);
|
|
2634
|
+
if (args2.file_path) return String(args2.file_path);
|
|
2635
|
+
if (args2.path) return String(args2.path);
|
|
2636
|
+
if (args2.pattern) return String(args2.pattern);
|
|
2637
|
+
if (args2.url) return String(args2.url);
|
|
2638
|
+
if (args2.query) return String(args2.query);
|
|
2639
|
+
if (args2.old_string) return `"${String(args2.old_string).slice(0, 30)}" \u2192 "${String(args2.new_string || "").slice(0, 30)}"`;
|
|
2640
|
+
if (args2.content) return `[${String(args2.content).length} chars]`;
|
|
2641
|
+
const keys = Object.keys(args2);
|
|
2642
|
+
if (keys.length > 0) return JSON.stringify(args2);
|
|
2643
|
+
return "";
|
|
2644
|
+
}
|
|
2645
|
+
function printToolCall(tc, detailed = false, model) {
|
|
2646
|
+
const color = SOURCE_COLOR[tc.source] || source_default.white;
|
|
2647
|
+
const label = SOURCE_LABEL[tc.source] || tc.source;
|
|
2648
|
+
const time = formatTime(tc.timestamp);
|
|
2649
|
+
const tool = truncate(tc.toolName, 16);
|
|
2650
|
+
const errorMark = tc.isError ? source_default.red(" ERR") : "";
|
|
2651
|
+
if (!detailed) {
|
|
2652
|
+
const argSummary = truncate(extractArgSummary(tc.arguments).replace(/[\n\r]/g, " "), 70);
|
|
2653
|
+
console.log(
|
|
2654
|
+
` ${source_default.dim(time)} ${color(label.padEnd(6))} ${source_default.white(tool.padEnd(17))} ${source_default.dim(argSummary)}${errorMark}`
|
|
2655
|
+
);
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
console.log(
|
|
2659
|
+
` ${source_default.dim(time)} ${color(label.padEnd(6))} ${source_default.white.bold(tool)}${errorMark}`
|
|
2660
|
+
);
|
|
2661
|
+
console.log(` ${" ".repeat(9)}${source_default.dim("ID:")} ${source_default.dim(tc.id.slice(0, 12))} ${source_default.dim("Session:")} ${source_default.dim(tc.sessionId.slice(0, 12))}${model ? " " + source_default.dim("Model:") + " " + source_default.dim(model) : ""}`);
|
|
2662
|
+
const argStr = extractArgSummary(tc.arguments).replace(/[\n\r]/g, " ");
|
|
2663
|
+
if (argStr.length > 0) {
|
|
2664
|
+
if (argStr.length <= 120) {
|
|
2665
|
+
console.log(` ${" ".repeat(9)}${source_default.cyan("\u25B8")} ${argStr}`);
|
|
2666
|
+
} else {
|
|
2667
|
+
const lines = argStr.match(/.{1,120}/g) || [argStr];
|
|
2668
|
+
console.log(` ${" ".repeat(9)}${source_default.cyan("\u25B8")} ${lines[0]}`);
|
|
2669
|
+
for (let i = 1; i < Math.min(lines.length, 4); i++) {
|
|
2670
|
+
console.log(` ${" ".repeat(11)}${lines[i]}`);
|
|
2671
|
+
}
|
|
2672
|
+
if (lines.length > 4) console.log(` ${" ".repeat(11)}${source_default.dim(`... +${lines.length - 4} more lines`)}`);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (tc.result) {
|
|
2676
|
+
const resultStr = tc.result.replace(/[\n\r]+/g, " ").trim();
|
|
2677
|
+
if (resultStr.length > 0) {
|
|
2678
|
+
const icon = tc.isError ? source_default.red("\u2718") : source_default.green("\u2714");
|
|
2679
|
+
const preview = truncate(resultStr, 120);
|
|
2680
|
+
console.log(` ${" ".repeat(9)}${icon} ${source_default.dim(preview)}`);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
console.log("");
|
|
2684
|
+
}
|
|
2685
|
+
function printLogs(data, limit = 50, detailed = true) {
|
|
2686
|
+
console.log("");
|
|
2687
|
+
console.log(source_default.bold(" Recent Tool Calls"));
|
|
2688
|
+
console.log(source_default.dim(" " + "\u2500".repeat(90)));
|
|
2689
|
+
if (!detailed) {
|
|
2690
|
+
console.log(source_default.dim(" Time Source Tool Arguments"));
|
|
2691
|
+
console.log(source_default.dim(" " + "\u2500".repeat(90)));
|
|
2692
|
+
}
|
|
2693
|
+
const sessionMap = new Map(data.sessions.map((s) => [s.id, s]));
|
|
2694
|
+
const allCalls = data.sessions.flatMap((s) => s.toolCalls).sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
2695
|
+
const recent = allCalls.slice(-limit);
|
|
2696
|
+
let lastDate = "";
|
|
2697
|
+
for (const tc of recent) {
|
|
2698
|
+
const date = formatDate(tc.timestamp);
|
|
2699
|
+
if (date !== lastDate) {
|
|
2700
|
+
lastDate = date;
|
|
2701
|
+
console.log(source_default.dim(`
|
|
2702
|
+
\u2500\u2500 ${date} \u2500\u2500`));
|
|
2703
|
+
}
|
|
2704
|
+
const session = sessionMap.get(tc.sessionId);
|
|
2705
|
+
printToolCall(tc, detailed, session?.model);
|
|
2706
|
+
}
|
|
2707
|
+
const errorCount = recent.filter((tc) => tc.isError).length;
|
|
2708
|
+
const sources = [...new Set(recent.map((tc) => tc.source))];
|
|
2709
|
+
console.log("");
|
|
2710
|
+
console.log(source_default.dim(` Showing last ${recent.length} of ${allCalls.length} total tool calls`));
|
|
2711
|
+
if (errorCount > 0) {
|
|
2712
|
+
console.log(source_default.dim(` ${errorCount} error(s) in view | Sources: ${sources.join(", ")}`));
|
|
2713
|
+
}
|
|
2714
|
+
console.log("");
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
// src/audit/export.ts
|
|
2718
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2719
|
+
import { resolve as resolve3 } from "path";
|
|
2720
|
+
function getFilePath(ext) {
|
|
2721
|
+
return resolve3(process.cwd(), `solongate-audit-report.${ext}`);
|
|
2722
|
+
}
|
|
2723
|
+
function exportJSON({ data, results }) {
|
|
2724
|
+
const { intScore } = calcScore(results);
|
|
2725
|
+
const allCalls = data.sessions.flatMap(
|
|
2726
|
+
(s) => s.toolCalls.map((tc) => ({
|
|
2727
|
+
...tc,
|
|
2728
|
+
model: s.model || null
|
|
2729
|
+
}))
|
|
2730
|
+
);
|
|
2731
|
+
const payload = {
|
|
2732
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2733
|
+
score: intScore,
|
|
2734
|
+
maxScore: 10,
|
|
2735
|
+
summary: {
|
|
2736
|
+
sources: data.sources,
|
|
2737
|
+
sessions: data.sessions.length,
|
|
2738
|
+
totalToolCalls: data.totalToolCalls,
|
|
2739
|
+
timeRange: data.timeRange
|
|
2740
|
+
},
|
|
2741
|
+
auditResults: results.map((r) => ({
|
|
2742
|
+
code: r.code,
|
|
2743
|
+
title: r.title,
|
|
2744
|
+
status: r.status,
|
|
2745
|
+
summary: r.summary,
|
|
2746
|
+
details: r.details,
|
|
2747
|
+
recommendation: r.recommendation || null,
|
|
2748
|
+
evidence: r.evidence
|
|
2749
|
+
})),
|
|
2750
|
+
sessions: data.sessions.map((s) => ({
|
|
2751
|
+
id: s.id,
|
|
2752
|
+
source: s.source,
|
|
2753
|
+
model: s.model || null,
|
|
2754
|
+
startTime: s.startTime,
|
|
2755
|
+
endTime: s.endTime || null,
|
|
2756
|
+
filePath: s.filePath,
|
|
2757
|
+
toolCallCount: s.toolCalls.length
|
|
2758
|
+
})),
|
|
2759
|
+
toolCalls: allCalls.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""))
|
|
2760
|
+
};
|
|
2761
|
+
const filePath = getFilePath("json");
|
|
2762
|
+
writeFileSync2(filePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
2763
|
+
return filePath;
|
|
2764
|
+
}
|
|
2765
|
+
function escapeCSV(value) {
|
|
2766
|
+
const str = String(value ?? "");
|
|
2767
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
|
|
2768
|
+
return '"' + str.replace(/"/g, '""') + '"';
|
|
2769
|
+
}
|
|
2770
|
+
return str;
|
|
2771
|
+
}
|
|
2772
|
+
function exportCSV({ data }) {
|
|
2773
|
+
const headers = ["timestamp", "source", "sessionId", "model", "toolCallId", "toolName", "arguments", "result", "isError"];
|
|
2774
|
+
const rows = [headers.join(",")];
|
|
2775
|
+
const sessionMap = new Map(data.sessions.map((s) => [s.id, s]));
|
|
2776
|
+
const allCalls = data.sessions.flatMap((s) => s.toolCalls).sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
2777
|
+
for (const tc of allCalls) {
|
|
2778
|
+
const session = sessionMap.get(tc.sessionId);
|
|
2779
|
+
rows.push([
|
|
2780
|
+
escapeCSV(tc.timestamp),
|
|
2781
|
+
escapeCSV(tc.source),
|
|
2782
|
+
escapeCSV(tc.sessionId),
|
|
2783
|
+
escapeCSV(session?.model || ""),
|
|
2784
|
+
escapeCSV(tc.id),
|
|
2785
|
+
escapeCSV(tc.toolName),
|
|
2786
|
+
escapeCSV(JSON.stringify(tc.arguments)),
|
|
2787
|
+
escapeCSV(tc.result || ""),
|
|
2788
|
+
escapeCSV(tc.isError ? "true" : "false")
|
|
2789
|
+
].join(","));
|
|
2790
|
+
}
|
|
2791
|
+
const filePath = getFilePath("csv");
|
|
2792
|
+
writeFileSync2(filePath, rows.join("\n"), "utf-8");
|
|
2793
|
+
return filePath;
|
|
2794
|
+
}
|
|
2795
|
+
function escapeHTML(str) {
|
|
2796
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2797
|
+
}
|
|
2798
|
+
function exportHTML({ data, results }) {
|
|
2799
|
+
const { intScore, fixCount } = calcScore(results);
|
|
2800
|
+
const allCalls = data.sessions.flatMap((s) => s.toolCalls).sort((a, b) => (b.timestamp || "").localeCompare(a.timestamp || ""));
|
|
2801
|
+
const sessionMap = new Map(data.sessions.map((s) => [s.id, s]));
|
|
2802
|
+
const errorCount = allCalls.filter((tc) => tc.isError).length;
|
|
2803
|
+
const statusIcon = {
|
|
2804
|
+
PROTECTED: "✅",
|
|
2805
|
+
PARTIAL: "⚠️",
|
|
2806
|
+
NOT_PROTECTED: "❌"
|
|
2807
|
+
};
|
|
2808
|
+
const sourceLabel = {
|
|
2809
|
+
claude: "Claude",
|
|
2810
|
+
gemini: "Gemini",
|
|
2811
|
+
openclaw: "OpenClaw"
|
|
2812
|
+
};
|
|
2813
|
+
const pillClass = {
|
|
2814
|
+
PROTECTED: "pill pill-green",
|
|
2815
|
+
PARTIAL: "pill pill-yellow",
|
|
2816
|
+
NOT_PROTECTED: "pill pill-red"
|
|
2817
|
+
};
|
|
2818
|
+
const auditRows = results.sort((a, b) => {
|
|
2819
|
+
const order = { PROTECTED: 0, PARTIAL: 1, NOT_PROTECTED: 2 };
|
|
2820
|
+
return (order[a.status] ?? 2) - (order[b.status] ?? 2);
|
|
2821
|
+
}).map((r) => `
|
|
2822
|
+
<tr>
|
|
2823
|
+
<td style="text-align:center;font-size:14px">${statusIcon[r.status]}</td>
|
|
2824
|
+
<td><span class="code-tag">${escapeHTML(r.code)}</span></td>
|
|
2825
|
+
<td class="cat">${escapeHTML(r.title)}</td>
|
|
2826
|
+
<td><span class="${pillClass[r.status]}">${r.status.replace(/_/g, " ")}</span></td>
|
|
2827
|
+
<td class="det">${escapeHTML(r.summary)}</td>
|
|
2828
|
+
<td class="det">${r.recommendation ? escapeHTML(r.recommendation) : ""}</td>
|
|
2829
|
+
</tr>`).join("");
|
|
2830
|
+
const toolRows = allCalls.map((tc) => {
|
|
2831
|
+
const session = sessionMap.get(tc.sessionId);
|
|
2832
|
+
const argFull = escapeHTML(JSON.stringify(tc.arguments, null, 2));
|
|
2833
|
+
const hasArgs = argFull !== "{}";
|
|
2834
|
+
const resStr = escapeHTML((tc.result || "").slice(0, 500));
|
|
2835
|
+
const src = tc.source;
|
|
2836
|
+
const srcClass = "src-" + src;
|
|
2837
|
+
const time = tc.timestamp ? new Date(tc.timestamp).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit" }) : "";
|
|
2838
|
+
const dateShort = tc.timestamp ? new Date(tc.timestamp).toLocaleDateString("en-GB", { day: "2-digit", month: "2-digit" }) : "";
|
|
2839
|
+
const dateFull = tc.timestamp ? new Date(tc.timestamp).toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" }) : "";
|
|
2840
|
+
return `
|
|
2841
|
+
<tr class="log-row ${tc.isError ? "err-row" : ""}" data-source="${src}" data-date="${dateFull}">
|
|
2842
|
+
<td class="ts"><span class="d">${dateShort}</span> ${time}</td>
|
|
2843
|
+
<td><span class="src-tag ${srcClass}">${sourceLabel[src] || src}</span></td>
|
|
2844
|
+
<td class="tool">${escapeHTML(tc.toolName)}${tc.isError ? '<span class="err-tag">ERR</span>' : ""}</td>
|
|
2845
|
+
<td class="args">${hasArgs ? `<div class="acc"><span class="acc-toggle">args</span><div class="acc-body"><div><pre>${argFull}</pre></div></div></div>` : '<span style="color:#2a2a2e">—</span>'}</td>
|
|
2846
|
+
<td>${resStr ? `<div class="acc"><span class="acc-toggle">${tc.isError ? '<span style="color:#f87171">error</span>' : "result"}</span><div class="acc-body"><div><pre>${resStr}</pre></div></div></div>` : '<span style="color:#2a2a2e">—</span>'}</td>
|
|
2847
|
+
<td class="meta">${escapeHTML(tc.id.slice(0, 8))}<br>${escapeHTML(session?.model || "-")}</td>
|
|
2848
|
+
</tr>`;
|
|
2849
|
+
}).join("");
|
|
2850
|
+
const scoreColor = intScore >= 7 ? "#22c55e" : intScore >= 4 ? "#eab308" : "#ef4444";
|
|
2851
|
+
const logoHtml = `<img src="https://cdn.solongate.com/icon-256x256.png" alt="SolonGate" width="36" height="36" style="border-radius:8px">`;
|
|
2852
|
+
const html = `<!DOCTYPE html>
|
|
2853
|
+
<html lang="en">
|
|
2854
|
+
<head>
|
|
2855
|
+
<meta charset="UTF-8">
|
|
2856
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2857
|
+
<link rel="icon" type="image/png" href="https://cdn.solongate.com/icon-32x32.png" sizes="32x32">
|
|
2858
|
+
<link rel="icon" type="image/png" href="https://cdn.solongate.com/icon-16x16.png" sizes="16x16">
|
|
2859
|
+
<link rel="apple-touch-icon" href="https://cdn.solongate.com/icon-192x192.png">
|
|
2860
|
+
<title>SolonGate Audit Report</title>
|
|
2861
|
+
<style>
|
|
2862
|
+
:root { --bg: #0c0c0e; --surface: #161618; --surface2: #1e1e21; --border: #2a2a2e; --text: #e8e8ec; --text2: #8b8b96; --accent: #7c8aff; }
|
|
2863
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2864
|
+
|
|
2865
|
+
/* Custom scrollbar \u2014 thin, dark, auto-hide */
|
|
2866
|
+
*::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
2867
|
+
*::-webkit-scrollbar-track { background: transparent; }
|
|
2868
|
+
*::-webkit-scrollbar-thumb { background: transparent; border-radius: 4px; transition: background 0.3s; }
|
|
2869
|
+
*:hover::-webkit-scrollbar-thumb { background: #333; }
|
|
2870
|
+
*::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
2871
|
+
*::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
|
|
2872
|
+
* { scrollbar-width: thin; scrollbar-color: transparent transparent; }
|
|
2873
|
+
*:hover { scrollbar-color: #333 transparent; }
|
|
2874
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; -webkit-font-smoothing: antialiased; }
|
|
2875
|
+
.wrap { max-width: 1360px; margin: 0 auto; padding: 48px 40px 80px; }
|
|
2876
|
+
|
|
2877
|
+
/* Header */
|
|
2878
|
+
.hdr { display: flex; align-items: center; gap: 14px; margin-bottom: 40px; }
|
|
2879
|
+
.hdr svg { flex-shrink: 0; }
|
|
2880
|
+
.hdr-text h1 { font-size: 20px; font-weight: 600; letter-spacing: -0.3px; color: #fff; }
|
|
2881
|
+
.hdr-text p { font-size: 12px; color: var(--text2); margin-top: 1px; }
|
|
2882
|
+
|
|
2883
|
+
/* Grid */
|
|
2884
|
+
.grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 28px; }
|
|
2885
|
+
@media (max-width: 800px) { .grid4 { grid-template-columns: repeat(2, 1fr); } }
|
|
2886
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 18px 20px; }
|
|
2887
|
+
.card-label { font-size: 10px; color: var(--text2); text-transform: uppercase; letter-spacing: 1.2px; font-weight: 500; }
|
|
2888
|
+
.card-val { font-size: 26px; font-weight: 700; margin-top: 4px; color: #fff; }
|
|
2889
|
+
.card-sub { font-size: 11px; color: var(--text2); margin-top: 2px; }
|
|
2890
|
+
|
|
2891
|
+
/* Score */
|
|
2892
|
+
.score-bar { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 24px 28px; margin-bottom: 28px; display: flex; align-items: center; gap: 24px; }
|
|
2893
|
+
.ring { width: 80px; height: 80px; position: relative; flex-shrink: 0; }
|
|
2894
|
+
.ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
|
|
2895
|
+
.ring circle { fill: none; stroke-width: 6; }
|
|
2896
|
+
.ring .track { stroke: var(--surface2); }
|
|
2897
|
+
.ring .fill { stroke: ${scoreColor}; stroke-linecap: round; stroke-dasharray: 0 251; }
|
|
2898
|
+
.ring .num { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700; color: ${scoreColor}; }
|
|
2899
|
+
.score-txt h3 { font-size: 15px; font-weight: 600; color: #fff; }
|
|
2900
|
+
.score-txt p { font-size: 13px; color: var(--text2); margin-top: 2px; }
|
|
2901
|
+
.score-txt a { color: ${scoreColor}; text-decoration: none; font-weight: 500; }
|
|
2902
|
+
|
|
2903
|
+
/* Section */
|
|
2904
|
+
.sec { margin-bottom: 28px; }
|
|
2905
|
+
.sec-hdr { font-size: 13px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
|
|
2906
|
+
.sec-count { background: var(--surface2); font-size: 10px; padding: 1px 7px; border-radius: 8px; font-weight: 600; color: var(--text2); }
|
|
2907
|
+
|
|
2908
|
+
/* Table */
|
|
2909
|
+
table { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
|
|
2910
|
+
th { text-align: left; padding: 10px 14px; background: var(--surface2); font-size: 10px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text2); font-weight: 600; }
|
|
2911
|
+
td { padding: 9px 14px; border-top: 1px solid var(--border); font-size: 12px; vertical-align: top; }
|
|
2912
|
+
tbody tr { transition: background 0.15s; }
|
|
2913
|
+
tbody tr:hover { background: rgba(124,138,255,0.04); }
|
|
2914
|
+
.err-row { background: rgba(239,68,68,0.05); }
|
|
2915
|
+
.err-row:hover { background: rgba(239,68,68,0.08); }
|
|
2916
|
+
|
|
2917
|
+
/* Badges */
|
|
2918
|
+
.pill { display: inline-block; padding: 2px 9px; border-radius: 4px; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; }
|
|
2919
|
+
.pill-green { background: rgba(34,197,94,0.12); color: #4ade80; }
|
|
2920
|
+
.pill-yellow { background: rgba(234,179,8,0.12); color: #facc15; }
|
|
2921
|
+
.pill-red { background: rgba(239,68,68,0.12); color: #f87171; }
|
|
2922
|
+
.code-tag { color: var(--accent); font-weight: 600; font-size: 11px; }
|
|
2923
|
+
.src-tag { display: inline-block; padding: 1px 7px; border-radius: 3px; font-size: 10px; font-weight: 600; }
|
|
2924
|
+
.src-claude { background: rgba(192,132,252,0.12); color: #c084fc; }
|
|
2925
|
+
.src-gemini { background: rgba(96,165,250,0.12); color: #60a5fa; }
|
|
2926
|
+
.src-openclaw { background: rgba(74,222,128,0.12); color: #4ade80; }
|
|
2927
|
+
.err-tag { background: rgba(239,68,68,0.15); color: #f87171; padding: 0 5px; border-radius: 3px; font-size: 9px; font-weight: 700; margin-left: 4px; }
|
|
2928
|
+
.cat { font-weight: 500; }
|
|
2929
|
+
.det { font-size: 11px; color: var(--text2); max-width: 280px; line-height: 1.4; }
|
|
2930
|
+
|
|
2931
|
+
/* Mono cells */
|
|
2932
|
+
.ts { white-space: nowrap; font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 11px; color: var(--text2); }
|
|
2933
|
+
.ts .d { color: #3f3f46; }
|
|
2934
|
+
.tool { font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 11px; font-weight: 500; white-space: nowrap; }
|
|
2935
|
+
.args { font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 11px; max-width: 380px; word-break: break-all; line-height: 1.4; }
|
|
2936
|
+
.meta { font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 10px; color: var(--text2); white-space: nowrap; }
|
|
2937
|
+
|
|
2938
|
+
/* Accordion \u2014 animated with CSS grid trick */
|
|
2939
|
+
.acc { margin-top: 4px; }
|
|
2940
|
+
.acc-toggle { cursor: pointer; color: var(--accent); font-size: 10px; font-weight: 500; user-select: none; display: inline-flex; align-items: center; gap: 3px; opacity: 0.7; transition: opacity 0.15s; }
|
|
2941
|
+
.acc-toggle:hover { opacity: 1; }
|
|
2942
|
+
.acc-toggle::before { content: '\\25B8'; font-size: 8px; transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); display: inline-block; }
|
|
2943
|
+
.acc-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s ease; opacity: 0; }
|
|
2944
|
+
.acc-body > div { overflow: hidden; }
|
|
2945
|
+
.acc.open .acc-toggle::before { transform: rotate(90deg); }
|
|
2946
|
+
.acc.open .acc-body { grid-template-rows: 1fr; opacity: 1; }
|
|
2947
|
+
.acc pre { background: var(--bg); padding: 10px 12px; border-radius: 6px; margin-top: 6px; font-size: 10px; max-height: 220px; overflow: auto; white-space: pre-wrap; word-break: break-all; border: 1px solid var(--border); color: var(--text2); line-height: 1.5; }
|
|
2948
|
+
|
|
2949
|
+
/* Filter */
|
|
2950
|
+
.filters { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
2951
|
+
.filters input, .filters select { background: var(--surface); border: 1px solid var(--border); color: var(--text); padding: 7px 12px; border-radius: 6px; font-size: 12px; outline: none; transition: border-color 0.2s; }
|
|
2952
|
+
.filters input:focus, .filters select:focus { border-color: var(--accent); }
|
|
2953
|
+
.filters input { flex: 1; min-width: 220px; }
|
|
2954
|
+
|
|
2955
|
+
/* Pagination */
|
|
2956
|
+
.pager { display: flex; align-items: center; justify-content: center; gap: 4px; margin-top: 16px; flex-wrap: wrap; }
|
|
2957
|
+
.pager button { background: var(--surface); border: 1px solid var(--border); color: var(--text2); padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; transition: background 0.15s, color 0.15s, border-color 0.15s; }
|
|
2958
|
+
.pager button:hover { background: var(--surface2); color: var(--text); }
|
|
2959
|
+
.pager button.active { background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600; }
|
|
2960
|
+
.pager button:disabled { opacity: 0.3; cursor: default; }
|
|
2961
|
+
.pager .pager-info { font-size: 11px; color: var(--text2); margin: 0 8px; }
|
|
2962
|
+
.date-sep { background: var(--surface2); }
|
|
2963
|
+
.date-sep td { padding: 6px 14px; font-size: 11px; font-weight: 600; color: var(--text2); letter-spacing: 0.5px; border: none; }
|
|
2964
|
+
|
|
2965
|
+
/* Skeleton loading */
|
|
2966
|
+
.skel-row td { padding: 12px 14px; }
|
|
2967
|
+
.skel-bar { height: 12px; border-radius: 4px; background: var(--surface2); position: relative; overflow: hidden; }
|
|
2968
|
+
.skel-bar::after { content: ''; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.03), transparent); animation: shimmer 1.8s infinite; }
|
|
2969
|
+
@keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
|
|
2970
|
+
.skel-w1 { width: 72px; }
|
|
2971
|
+
.skel-w2 { width: 52px; }
|
|
2972
|
+
.skel-w3 { width: 88px; }
|
|
2973
|
+
.skel-w4 { width: 120px; }
|
|
2974
|
+
.skel-w5 { width: 90px; }
|
|
2975
|
+
.skel-w6 { width: 60px; }
|
|
2976
|
+
#lt tbody.loading .log-row { display: none; }
|
|
2977
|
+
#lt tbody.loading .date-sep { display: none; }
|
|
2978
|
+
|
|
2979
|
+
/* Footer */
|
|
2980
|
+
.ftr { text-align: center; margin-top: 56px; padding-top: 24px; border-top: 1px solid var(--border); }
|
|
2981
|
+
.ftr a { color: var(--text2); text-decoration: none; font-size: 12px; font-weight: 500; transition: color 0.15s; }
|
|
2982
|
+
.ftr a:hover { color: #fff; }
|
|
2983
|
+
.ftr p { color: #3f3f46; font-size: 11px; margin-top: 4px; }
|
|
2984
|
+
|
|
2985
|
+
@media print {
|
|
2986
|
+
body { background: #fff; color: #111; }
|
|
2987
|
+
.wrap { padding: 16px; }
|
|
2988
|
+
.card, .score-bar, table { background: #fff; border-color: #e5e5e5; }
|
|
2989
|
+
th { background: #f5f5f5; color: #333; }
|
|
2990
|
+
td { border-color: #eee; }
|
|
2991
|
+
.acc pre { background: #f8f8f8; border-color: #e5e5e5; }
|
|
2992
|
+
tbody tr:hover { background: transparent; }
|
|
2993
|
+
.err-row { background: #fef2f2; }
|
|
2994
|
+
.filters { display: none; }
|
|
2995
|
+
@page { size: A4 landscape; margin: 8mm; }
|
|
2996
|
+
}
|
|
2997
|
+
</style>
|
|
2998
|
+
</head>
|
|
2999
|
+
<body>
|
|
3000
|
+
<div class="wrap">
|
|
3001
|
+
|
|
3002
|
+
<div class="hdr">
|
|
3003
|
+
${logoHtml}
|
|
3004
|
+
<div class="hdr-text">
|
|
3005
|
+
<h1>Security Audit</h1>
|
|
3006
|
+
<p>OWASP Agentic Top 10 · ${(/* @__PURE__ */ new Date()).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" })}</p>
|
|
3007
|
+
</div>
|
|
3008
|
+
</div>
|
|
3009
|
+
|
|
3010
|
+
<div class="grid4">
|
|
3011
|
+
<div class="card"><div class="card-label">AI Tools</div><div class="card-val">${data.sources.length}</div><div class="card-sub">${data.sources.join(", ") || "None"}</div></div>
|
|
3012
|
+
<div class="card"><div class="card-label">Sessions</div><div class="card-val">${data.sessions.length}</div><div class="card-sub">${data.timeRange ? new Date(data.timeRange.from).toLocaleDateString("en-GB", { day: "numeric", month: "short" }) + " – " + new Date(data.timeRange.to).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "-"}</div></div>
|
|
3013
|
+
<div class="card"><div class="card-label">Tool Calls</div><div class="card-val">${data.totalToolCalls.toLocaleString()}</div><div class="card-sub">${errorCount > 0 ? errorCount + " error" + (errorCount > 1 ? "s" : "") : "No errors"}</div></div>
|
|
3014
|
+
<div class="card"><div class="card-label">Critical Issues</div><div class="card-val" style="color:${fixCount > 0 ? "#f87171" : "#4ade80"}">${fixCount}</div><div class="card-sub">${fixCount > 0 ? "need attention" : "none"}</div></div>
|
|
3015
|
+
</div>
|
|
3016
|
+
|
|
3017
|
+
<div class="score-bar">
|
|
3018
|
+
<div class="ring">
|
|
3019
|
+
<svg viewBox="0 0 100 100"><circle class="track" cx="50" cy="50" r="40"/><circle class="fill" cx="50" cy="50" r="40"/></svg>
|
|
3020
|
+
<div class="num">${intScore}</div>
|
|
3021
|
+
</div>
|
|
3022
|
+
<div class="score-txt">
|
|
3023
|
+
<h3>${intScore}/10</h3>
|
|
3024
|
+
<p>${intScore >= 7 ? "Good protection." : intScore >= 4 ? "Several categories need attention." : "Critical gaps across multiple categories."}</p>
|
|
3025
|
+
${fixCount > 0 ? `<p style="margin-top:6px"><a href="https://solongate.com">Fix ${fixCount} issue${fixCount > 1 ? "s" : ""} →</a></p>` : ""}
|
|
3026
|
+
</div>
|
|
3027
|
+
</div>
|
|
3028
|
+
|
|
3029
|
+
<div class="sec">
|
|
3030
|
+
<div class="sec-hdr">Audit Results <span class="sec-count">${results.length}</span></div>
|
|
3031
|
+
<table>
|
|
3032
|
+
<thead><tr><th style="width:32px"></th><th>Code</th><th>Category</th><th>Status</th><th>Details</th><th>Recommendation</th></tr></thead>
|
|
3033
|
+
<tbody>${auditRows}</tbody>
|
|
3034
|
+
</table>
|
|
3035
|
+
</div>
|
|
3036
|
+
|
|
3037
|
+
<div class="sec">
|
|
3038
|
+
<div class="sec-hdr">Tool Calls <span class="sec-count">${allCalls.length.toLocaleString()}</span></div>
|
|
3039
|
+
<div class="filters">
|
|
3040
|
+
<input type="text" id="q" placeholder="Search..." oninput="applyFilters()">
|
|
3041
|
+
<select id="sf" onchange="applyFilters()"><option value="">All sources</option><option value="claude">Claude</option><option value="gemini">Gemini</option><option value="openclaw">OpenClaw</option></select>
|
|
3042
|
+
<select id="ef" onchange="applyFilters()"><option value="">All</option><option value="e">Errors</option><option value="s">Success</option></select>
|
|
3043
|
+
<select id="df" onchange="applyFilters()"><option value="">All dates</option></select>
|
|
3044
|
+
</div>
|
|
3045
|
+
<table id="lt">
|
|
3046
|
+
<thead><tr><th>Time</th><th>Source</th><th>Tool</th><th>Arguments</th><th>Result</th><th>Meta</th></tr></thead>
|
|
3047
|
+
<tbody class="loading">
|
|
3048
|
+
${Array.from({ length: 12 }, () => `<tr class="skel-row"><td><div class="skel-bar skel-w1"></div></td><td><div class="skel-bar skel-w2"></div></td><td><div class="skel-bar skel-w3"></div></td><td><div class="skel-bar skel-w4"></div></td><td><div class="skel-bar skel-w5"></div></td><td><div class="skel-bar skel-w6"></div></td></tr>`).join("")}
|
|
3049
|
+
${toolRows}
|
|
3050
|
+
</tbody>
|
|
3051
|
+
</table>
|
|
3052
|
+
<div class="pager" id="pager"></div>
|
|
3053
|
+
</div>
|
|
3054
|
+
|
|
3055
|
+
<div class="ftr">
|
|
3056
|
+
<a href="https://solongate.com">solongate.com</a>
|
|
3057
|
+
<p>AI Agent Security</p>
|
|
3058
|
+
</div>
|
|
3059
|
+
|
|
3060
|
+
</div>
|
|
3061
|
+
<script>
|
|
3062
|
+
(function(){
|
|
3063
|
+
var PER_PAGE = 100;
|
|
3064
|
+
var page = 1;
|
|
3065
|
+
var allRows = Array.from(document.querySelectorAll('#lt tbody tr.log-row'));
|
|
3066
|
+
var filtered = allRows.slice();
|
|
3067
|
+
var tbody = document.querySelector('#lt tbody');
|
|
3068
|
+
|
|
3069
|
+
// Populate date dropdown
|
|
3070
|
+
var dates = [];
|
|
3071
|
+
var seen = {};
|
|
3072
|
+
allRows.forEach(function(r){
|
|
3073
|
+
var d = r.getAttribute('data-date');
|
|
3074
|
+
if(d && !seen[d]){ seen[d]=1; dates.push(d); }
|
|
3075
|
+
});
|
|
3076
|
+
var df = document.getElementById('df');
|
|
3077
|
+
dates.forEach(function(d){
|
|
3078
|
+
var o = document.createElement('option');
|
|
3079
|
+
o.value = d; o.textContent = d;
|
|
3080
|
+
df.appendChild(o);
|
|
3081
|
+
});
|
|
3082
|
+
|
|
3083
|
+
// Accordion
|
|
3084
|
+
document.addEventListener('click',function(e){
|
|
3085
|
+
var t=e.target.closest('.acc-toggle');
|
|
3086
|
+
if(t){t.closest('.acc').classList.toggle('open');}
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
function applyFilters(){
|
|
3090
|
+
var q = document.getElementById('q').value.toLowerCase();
|
|
3091
|
+
var s = document.getElementById('sf').value;
|
|
3092
|
+
var ef = document.getElementById('ef').value;
|
|
3093
|
+
var dv = document.getElementById('df').value;
|
|
3094
|
+
filtered = allRows.filter(function(r){
|
|
3095
|
+
var txt = r.textContent.toLowerCase();
|
|
3096
|
+
var src = r.getAttribute('data-source')||'';
|
|
3097
|
+
var dt = r.getAttribute('data-date')||'';
|
|
3098
|
+
var err = r.classList.contains('err-row');
|
|
3099
|
+
return (!q||txt.indexOf(q)!==-1)&&(!s||src===s)&&(!dv||dt===dv)&&(!ef||(ef==='e'&&err)||(ef==='s'&&!err));
|
|
3100
|
+
});
|
|
3101
|
+
page = 1;
|
|
3102
|
+
render();
|
|
3103
|
+
}
|
|
3104
|
+
window.applyFilters = applyFilters;
|
|
3105
|
+
|
|
3106
|
+
function render(){
|
|
3107
|
+
var total = filtered.length;
|
|
3108
|
+
var pages = Math.max(1, Math.ceil(total / PER_PAGE));
|
|
3109
|
+
if(page > pages) page = pages;
|
|
3110
|
+
var start = (page-1)*PER_PAGE;
|
|
3111
|
+
var end = Math.min(start+PER_PAGE, total);
|
|
3112
|
+
var slice = filtered.slice(start, end);
|
|
3113
|
+
|
|
3114
|
+
// Clear tbody and insert date-grouped rows
|
|
3115
|
+
tbody.innerHTML = '';
|
|
3116
|
+
var lastDate = '';
|
|
3117
|
+
slice.forEach(function(r){
|
|
3118
|
+
var d = r.getAttribute('data-date')||'';
|
|
3119
|
+
if(d && d !== lastDate){
|
|
3120
|
+
lastDate = d;
|
|
3121
|
+
var sep = document.createElement('tr');
|
|
3122
|
+
sep.className = 'date-sep';
|
|
3123
|
+
sep.innerHTML = '<td colspan="6">' + d + '</td>';
|
|
3124
|
+
tbody.appendChild(sep);
|
|
3125
|
+
}
|
|
3126
|
+
tbody.appendChild(r);
|
|
3127
|
+
});
|
|
3128
|
+
|
|
3129
|
+
// Pagination controls
|
|
3130
|
+
var pager = document.getElementById('pager');
|
|
3131
|
+
var html = '';
|
|
3132
|
+
html += '<button '+(page<=1?'disabled':'')+' onclick="pg('+(page-1)+')">‹</button>';
|
|
3133
|
+
var s = Math.max(1, page-3), e = Math.min(pages, page+3);
|
|
3134
|
+
if(s > 1) html += '<button onclick="pg(1)">1</button>';
|
|
3135
|
+
if(s > 2) html += '<span class="pager-info">…</span>';
|
|
3136
|
+
for(var i=s;i<=e;i++){
|
|
3137
|
+
html += '<button class="'+(i===page?'active':'')+'" onclick="pg('+i+')">'+i+'</button>';
|
|
3138
|
+
}
|
|
3139
|
+
if(e < pages-1) html += '<span class="pager-info">…</span>';
|
|
3140
|
+
if(e < pages) html += '<button onclick="pg('+pages+')">'+pages+'</button>';
|
|
3141
|
+
html += '<button '+(page>=pages?'disabled':'')+' onclick="pg('+(page+1)+')">›</button>';
|
|
3142
|
+
html += '<span class="pager-info">' + (start+1) + '–' + end + ' of ' + total + '</span>';
|
|
3143
|
+
pager.innerHTML = html;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
window.pg = function(p){ page=p; render(); window.scrollTo({top:document.getElementById('lt').offsetTop-80,behavior:'smooth'}); };
|
|
3147
|
+
|
|
3148
|
+
// Score ring animation
|
|
3149
|
+
requestAnimationFrame(function(){setTimeout(function(){
|
|
3150
|
+
var c=document.querySelector('.ring .fill');
|
|
3151
|
+
if(c) c.style.strokeDasharray='${Math.round(intScore / 10 * 251)} 251';
|
|
3152
|
+
},100)});
|
|
3153
|
+
|
|
3154
|
+
// Remove skeleton, show real rows
|
|
3155
|
+
tbody.classList.remove('loading');
|
|
3156
|
+
document.querySelectorAll('.skel-row').forEach(function(r){ r.remove(); });
|
|
3157
|
+
render();
|
|
3158
|
+
})();
|
|
3159
|
+
</script>
|
|
3160
|
+
</body>
|
|
3161
|
+
</html>`;
|
|
3162
|
+
const filePath = getFilePath("html");
|
|
3163
|
+
writeFileSync2(filePath, html, "utf-8");
|
|
3164
|
+
return filePath;
|
|
3165
|
+
}
|
|
3166
|
+
function exportPDF(payload) {
|
|
3167
|
+
const { data, results } = payload;
|
|
3168
|
+
const htmlPath = exportHTML(payload);
|
|
3169
|
+
const pdfHtml = `<!DOCTYPE html>
|
|
3170
|
+
<html><head>
|
|
3171
|
+
<meta charset="UTF-8">
|
|
3172
|
+
<meta http-equiv="refresh" content="0;url=${htmlPath.replace(/\\/g, "/")}">
|
|
3173
|
+
</head>
|
|
3174
|
+
<body>
|
|
3175
|
+
<p>Opening report... If not redirected, <a href="${htmlPath.replace(/\\/g, "/")}">click here</a> and press Ctrl+P to save as PDF.</p>
|
|
3176
|
+
</body></html>`;
|
|
3177
|
+
const filePath = getFilePath("pdf.html");
|
|
3178
|
+
writeFileSync2(filePath, pdfHtml, "utf-8");
|
|
3179
|
+
return filePath;
|
|
3180
|
+
}
|
|
3181
|
+
function exportAll(payload) {
|
|
3182
|
+
return [
|
|
3183
|
+
exportJSON(payload),
|
|
3184
|
+
exportCSV(payload),
|
|
3185
|
+
exportHTML(payload),
|
|
3186
|
+
exportPDF(payload)
|
|
3187
|
+
];
|
|
3188
|
+
}
|
|
3189
|
+
function runExport(format, payload) {
|
|
3190
|
+
console.log("");
|
|
3191
|
+
if (format === "all") {
|
|
3192
|
+
const files = exportAll(payload);
|
|
3193
|
+
for (const f of files) {
|
|
3194
|
+
console.log(` ${source_default.green("\u2714")} ${f}`);
|
|
3195
|
+
}
|
|
3196
|
+
} else {
|
|
3197
|
+
let filePath;
|
|
3198
|
+
switch (format) {
|
|
3199
|
+
case "json":
|
|
3200
|
+
filePath = exportJSON(payload);
|
|
3201
|
+
break;
|
|
3202
|
+
case "csv":
|
|
3203
|
+
filePath = exportCSV(payload);
|
|
3204
|
+
break;
|
|
3205
|
+
case "html":
|
|
3206
|
+
filePath = exportHTML(payload);
|
|
3207
|
+
break;
|
|
3208
|
+
case "pdf":
|
|
3209
|
+
filePath = exportPDF(payload);
|
|
3210
|
+
break;
|
|
3211
|
+
default:
|
|
3212
|
+
console.log(source_default.red(` Unknown format: ${format}`));
|
|
3213
|
+
console.log(source_default.dim(" Supported: json, csv, html, pdf, all"));
|
|
3214
|
+
process.exit(1);
|
|
3215
|
+
}
|
|
3216
|
+
console.log(` ${source_default.green("\u2714")} Exported: ${filePath}`);
|
|
3217
|
+
}
|
|
3218
|
+
console.log("");
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// src/audit/spinner.ts
|
|
3222
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3223
|
+
var Spinner = class {
|
|
3224
|
+
interval = null;
|
|
3225
|
+
frame = 0;
|
|
3226
|
+
message;
|
|
3227
|
+
constructor(message) {
|
|
3228
|
+
this.message = message;
|
|
3229
|
+
}
|
|
3230
|
+
start() {
|
|
3231
|
+
this.frame = 0;
|
|
3232
|
+
this.interval = setInterval(() => {
|
|
3233
|
+
const icon = source_default.cyan(FRAMES[this.frame % FRAMES.length]);
|
|
3234
|
+
process.stdout.write(`\r ${icon} ${source_default.dim(this.message)}`);
|
|
3235
|
+
this.frame++;
|
|
3236
|
+
}, 80);
|
|
3237
|
+
return this;
|
|
3238
|
+
}
|
|
3239
|
+
update(message) {
|
|
3240
|
+
this.message = message;
|
|
3241
|
+
}
|
|
3242
|
+
stop(finalMessage) {
|
|
3243
|
+
if (this.interval) {
|
|
3244
|
+
clearInterval(this.interval);
|
|
3245
|
+
this.interval = null;
|
|
3246
|
+
}
|
|
3247
|
+
process.stdout.write("\r\x1B[2K");
|
|
3248
|
+
if (finalMessage) {
|
|
3249
|
+
console.log(` ${source_default.green("\u2714")} ${source_default.dim(finalMessage)}`);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
};
|
|
3253
|
+
|
|
3254
|
+
// src/audit/index.ts
|
|
3255
|
+
var args = process.argv.slice(2);
|
|
3256
|
+
if (args.includes("--add-dir")) {
|
|
3257
|
+
const idx = args.indexOf("--add-dir");
|
|
3258
|
+
const dir = args[idx + 1];
|
|
3259
|
+
if (!dir) {
|
|
3260
|
+
console.log(" Usage: solongate-audit --add-dir <path>");
|
|
3261
|
+
process.exit(1);
|
|
3262
|
+
}
|
|
3263
|
+
addDir(dir);
|
|
3264
|
+
process.exit(0);
|
|
3265
|
+
}
|
|
3266
|
+
if (args.includes("--remove-dir")) {
|
|
3267
|
+
const idx = args.indexOf("--remove-dir");
|
|
3268
|
+
const dir = args[idx + 1];
|
|
3269
|
+
if (!dir) {
|
|
3270
|
+
console.log(" Usage: solongate-audit --remove-dir <path>");
|
|
3271
|
+
process.exit(1);
|
|
3272
|
+
}
|
|
3273
|
+
removeDir(dir);
|
|
3274
|
+
process.exit(0);
|
|
3275
|
+
}
|
|
3276
|
+
if (args.includes("--list-dirs")) {
|
|
3277
|
+
listDirs();
|
|
3278
|
+
process.exit(0);
|
|
3279
|
+
}
|
|
3280
|
+
if (args.includes("--search")) {
|
|
3281
|
+
searchLogs();
|
|
3282
|
+
process.exit(0);
|
|
3283
|
+
}
|
|
3284
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3285
|
+
console.log(`
|
|
3286
|
+
solongate-audit \u2014 AI agent audit log tool
|
|
3287
|
+
|
|
3288
|
+
Usage:
|
|
3289
|
+
npx solongate-audit Scan logs and show report
|
|
3290
|
+
npx solongate-audit --detailed Show detailed analysis per category
|
|
3291
|
+
npx solongate-audit --logs Show recent tool calls (last 50)
|
|
3292
|
+
npx solongate-audit --logs 100 Show last N tool calls
|
|
3293
|
+
npx solongate-audit --watch Live monitoring with log feed
|
|
3294
|
+
npx solongate-audit --json Machine-readable JSON output
|
|
3295
|
+
|
|
3296
|
+
Export:
|
|
3297
|
+
npx solongate-audit --export json Export full report as JSON
|
|
3298
|
+
npx solongate-audit --export csv Export tool calls as CSV
|
|
3299
|
+
npx solongate-audit --export html Export visual HTML report
|
|
3300
|
+
npx solongate-audit --export pdf Export PDF-ready HTML (print to PDF)
|
|
3301
|
+
npx solongate-audit --export all Export all formats at once
|
|
3302
|
+
|
|
3303
|
+
Log directories:
|
|
3304
|
+
npx solongate-audit --search Search system for AI tool logs
|
|
3305
|
+
npx solongate-audit --list-dirs Show all log directories
|
|
3306
|
+
npx solongate-audit --add-dir <path> Add a custom log directory
|
|
3307
|
+
npx solongate-audit --remove-dir <path> Remove a custom log directory
|
|
3308
|
+
|
|
3309
|
+
Config: ~/.solongate-audit/config.json
|
|
3310
|
+
`);
|
|
3311
|
+
process.exit(0);
|
|
3312
|
+
}
|
|
3313
|
+
var showDetailed = args.includes("--detailed") || args.includes("-d");
|
|
3314
|
+
var showJson = args.includes("--json");
|
|
3315
|
+
var showWatch = args.includes("--watch") || args.includes("-w");
|
|
3316
|
+
var showLogs = args.includes("--logs") || args.includes("-l");
|
|
3317
|
+
var showExport = args.includes("--export") || args.includes("-e");
|
|
3318
|
+
function getLogLimit() {
|
|
3319
|
+
const idx = args.indexOf("--logs") !== -1 ? args.indexOf("--logs") : args.indexOf("-l");
|
|
3320
|
+
if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("-")) {
|
|
3321
|
+
return parseInt(args[idx + 1], 10) || 50;
|
|
3322
|
+
}
|
|
3323
|
+
return 50;
|
|
3324
|
+
}
|
|
3325
|
+
function runAudit(silent = false) {
|
|
3326
|
+
const spinner = silent ? null : new Spinner("Scanning AI tool logs...");
|
|
3327
|
+
spinner?.start();
|
|
3328
|
+
const data = collectLogs();
|
|
3329
|
+
spinner?.update("Running OWASP Agentic Top 10 checks...");
|
|
3330
|
+
const results = runAllChecks(data);
|
|
3331
|
+
const { intScore } = calcScore(results);
|
|
3332
|
+
spinner?.stop(`Scanned ${data.totalToolCalls} tool calls across ${data.sessions.length} sessions`);
|
|
3333
|
+
return { data, results, intScore };
|
|
3334
|
+
}
|
|
3335
|
+
function printReport(data, results) {
|
|
3336
|
+
printHeader();
|
|
3337
|
+
printLogSummary(data);
|
|
3338
|
+
printCompactReport(results);
|
|
3339
|
+
printScore(results);
|
|
3340
|
+
if (showDetailed) {
|
|
3341
|
+
printDetailedReport(results);
|
|
3342
|
+
printFooter(results);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
if (showExport) {
|
|
3346
|
+
const idx = args.indexOf("--export") !== -1 ? args.indexOf("--export") : args.indexOf("-e");
|
|
3347
|
+
const format = args[idx + 1];
|
|
3348
|
+
if (!format || format.startsWith("-")) {
|
|
3349
|
+
console.log(" Usage: solongate-audit --export <json|csv|html|pdf|all>");
|
|
3350
|
+
process.exit(1);
|
|
3351
|
+
}
|
|
3352
|
+
const { data, results } = runAudit();
|
|
3353
|
+
printReport(data, results);
|
|
3354
|
+
runExport(format, { data, results });
|
|
3355
|
+
process.exit(0);
|
|
3356
|
+
}
|
|
3357
|
+
if (showWatch) {
|
|
3358
|
+
let lastToolCount = 0;
|
|
3359
|
+
let lastSessionCount = 0;
|
|
3360
|
+
let seenIds = /* @__PURE__ */ new Set();
|
|
3361
|
+
const init = () => {
|
|
3362
|
+
const { data, results } = runAudit();
|
|
3363
|
+
lastToolCount = data.totalToolCalls;
|
|
3364
|
+
lastSessionCount = data.sessions.length;
|
|
3365
|
+
for (const tc of data.sessions.flatMap((s) => s.toolCalls)) {
|
|
3366
|
+
seenIds.add(tc.id + tc.timestamp);
|
|
3367
|
+
}
|
|
3368
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
3369
|
+
printReport(data, results);
|
|
3370
|
+
const allCalls = data.sessions.flatMap((s) => s.toolCalls).sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
3371
|
+
const last10 = allCalls.slice(-10);
|
|
3372
|
+
console.log("");
|
|
3373
|
+
console.log(source_default.bold(" Live Log Feed"));
|
|
3374
|
+
console.log(source_default.dim(" " + "\u2500".repeat(80)));
|
|
3375
|
+
console.log(source_default.dim(" Time Source Tool Arguments"));
|
|
3376
|
+
console.log(source_default.dim(" " + "\u2500".repeat(80)));
|
|
3377
|
+
for (const tc of last10) {
|
|
3378
|
+
printToolCall(tc, true);
|
|
3379
|
+
}
|
|
3380
|
+
console.log("");
|
|
3381
|
+
console.log(source_default.dim(" Waiting for new tool calls..."));
|
|
3382
|
+
};
|
|
3383
|
+
init();
|
|
3384
|
+
setInterval(() => {
|
|
3385
|
+
const { data, results } = runAudit(true);
|
|
3386
|
+
if (data.totalToolCalls !== lastToolCount || data.sessions.length !== lastSessionCount) {
|
|
3387
|
+
const newCalls = data.sessions.flatMap((s) => s.toolCalls).filter((tc) => !seenIds.has(tc.id + tc.timestamp)).sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
3388
|
+
if (newCalls.length > 0) {
|
|
3389
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
3390
|
+
for (const tc of newCalls) {
|
|
3391
|
+
seenIds.add(tc.id + tc.timestamp);
|
|
3392
|
+
printToolCall(tc, true);
|
|
3393
|
+
}
|
|
3394
|
+
const { intScore } = calcScore(results);
|
|
3395
|
+
const errorCount = data.sessions.flatMap((s) => s.toolCalls).filter((tc) => tc.isError).length;
|
|
3396
|
+
console.log("");
|
|
3397
|
+
console.log(source_default.dim(` ${data.totalToolCalls} calls | ${data.sessions.length} sessions | ${errorCount} errors | Score: ${intScore}/10 | LIVE`));
|
|
3398
|
+
}
|
|
3399
|
+
lastToolCount = data.totalToolCalls;
|
|
3400
|
+
lastSessionCount = data.sessions.length;
|
|
3401
|
+
}
|
|
3402
|
+
}, 2e3);
|
|
3403
|
+
} else if (showLogs) {
|
|
3404
|
+
const { data } = runAudit();
|
|
3405
|
+
printHeader();
|
|
3406
|
+
printLogSummary(data);
|
|
3407
|
+
printLogs(data, getLogLimit());
|
|
3408
|
+
} else {
|
|
3409
|
+
const { data, results, intScore } = runAudit(showJson);
|
|
3410
|
+
if (showJson) {
|
|
3411
|
+
console.log(JSON.stringify({
|
|
3412
|
+
score: intScore,
|
|
3413
|
+
maxScore: 10,
|
|
3414
|
+
results,
|
|
3415
|
+
summary: {
|
|
3416
|
+
sources: data.sources,
|
|
3417
|
+
sessions: data.sessions.length,
|
|
3418
|
+
totalToolCalls: data.totalToolCalls,
|
|
3419
|
+
timeRange: data.timeRange
|
|
3420
|
+
}
|
|
3421
|
+
}, null, 2));
|
|
3422
|
+
} else {
|
|
3423
|
+
printReport(data, results);
|
|
3424
|
+
}
|
|
3425
|
+
process.exit(intScore >= 7 ? 0 : 1);
|
|
3426
|
+
}
|