codebot-ai 1.8.0 → 2.0.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/README.md +42 -3
- package/dist/agent.d.ts +10 -0
- package/dist/agent.js +55 -11
- package/dist/cli.js +83 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +9 -1
- package/dist/metrics.d.ts +60 -0
- package/dist/metrics.js +296 -0
- package/dist/risk.d.ts +52 -0
- package/dist/risk.js +367 -0
- package/dist/sarif.d.ts +82 -0
- package/dist/sarif.js +176 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/risk.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RiskScorer for CodeBot v1.9.0
|
|
4
|
+
*
|
|
5
|
+
* Per-tool-call risk assessment using 6-factor weighted scoring (0–100).
|
|
6
|
+
* Factors: tool permission, file path sensitivity, command destructiveness,
|
|
7
|
+
* network access, data volume, cumulative session risk.
|
|
8
|
+
*
|
|
9
|
+
* Levels: green (0–25), yellow (26–50), orange (51–75), red (76+).
|
|
10
|
+
* NEVER throws — risk scoring failures must not crash the agent.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.RiskScorer = void 0;
|
|
14
|
+
// ── Constants ──
|
|
15
|
+
const SENSITIVE_PATH_PATTERNS = [
|
|
16
|
+
/\.env($|\.)/,
|
|
17
|
+
/credentials/i,
|
|
18
|
+
/secrets?\b/i,
|
|
19
|
+
/\.pem$/,
|
|
20
|
+
/\.key$/,
|
|
21
|
+
/\.p12$/,
|
|
22
|
+
/\.pfx$/,
|
|
23
|
+
/id_rsa/,
|
|
24
|
+
/id_ed25519/,
|
|
25
|
+
/\.ssh\//,
|
|
26
|
+
/\/etc\/(passwd|shadow|sudoers)/,
|
|
27
|
+
/\/etc\/ssl/,
|
|
28
|
+
/\/System\//,
|
|
29
|
+
/\/Windows\/System32/,
|
|
30
|
+
/node_modules\//,
|
|
31
|
+
/package-lock\.json$/,
|
|
32
|
+
];
|
|
33
|
+
const MODERATE_PATH_PATTERNS = [
|
|
34
|
+
/\.config\//,
|
|
35
|
+
/\.gitconfig$/,
|
|
36
|
+
/\.npmrc$/,
|
|
37
|
+
/\.bashrc$/,
|
|
38
|
+
/\.zshrc$/,
|
|
39
|
+
/\.profile$/,
|
|
40
|
+
/tsconfig\.json$/,
|
|
41
|
+
/package\.json$/,
|
|
42
|
+
];
|
|
43
|
+
const DESTRUCTIVE_COMMANDS = [
|
|
44
|
+
/\brm\s+(-rf|-fr|--recursive)/,
|
|
45
|
+
/\brm\b.*\s+\//,
|
|
46
|
+
/\bchmod\s+[0-7]{3,4}/,
|
|
47
|
+
/\bchown\b/,
|
|
48
|
+
/\bkill\s+-9/,
|
|
49
|
+
/\bkillall\b/,
|
|
50
|
+
/\bpkill\b/,
|
|
51
|
+
/\bdrop\s+(table|database|schema)/i,
|
|
52
|
+
/\btruncate\s+table/i,
|
|
53
|
+
/\bdelete\s+from/i,
|
|
54
|
+
/\bgit\s+push\s+.*--force/,
|
|
55
|
+
/\bgit\s+reset\s+--hard/,
|
|
56
|
+
/\bgit\s+clean\s+-fd/,
|
|
57
|
+
/\bformat\s+[a-z]:/i,
|
|
58
|
+
/\bmkfs\b/,
|
|
59
|
+
/\bdd\s+if=/,
|
|
60
|
+
/>\s*\/dev\/sd[a-z]/,
|
|
61
|
+
];
|
|
62
|
+
const MODERATE_COMMANDS = [
|
|
63
|
+
/\brm\b/,
|
|
64
|
+
/\bmv\b/,
|
|
65
|
+
/\bgit\s+push\b/,
|
|
66
|
+
/\bgit\s+checkout\b/,
|
|
67
|
+
/\bgit\s+merge\b/,
|
|
68
|
+
/\bgit\s+rebase\b/,
|
|
69
|
+
/\bnpm\s+(install|uninstall|update)\b/,
|
|
70
|
+
/\bpip\s+install\b/,
|
|
71
|
+
/\bcurl\b.*\|\s*(sh|bash)\b/,
|
|
72
|
+
/\bwget\b.*\|\s*(sh|bash)\b/,
|
|
73
|
+
/\bsudo\b/,
|
|
74
|
+
];
|
|
75
|
+
const SAFE_COMMANDS = [
|
|
76
|
+
/\bls\b/,
|
|
77
|
+
/\bcat\b/,
|
|
78
|
+
/\becho\b/,
|
|
79
|
+
/\bpwd\b/,
|
|
80
|
+
/\bwhoami\b/,
|
|
81
|
+
/\bdate\b/,
|
|
82
|
+
/\bgit\s+(status|log|diff|branch)\b/,
|
|
83
|
+
/\bnpm\s+(test|run|start)\b/,
|
|
84
|
+
/\bnode\b/,
|
|
85
|
+
/\btsc\b/,
|
|
86
|
+
/\bpython\b.*\.py/,
|
|
87
|
+
];
|
|
88
|
+
const NETWORK_TOOLS = new Set(['web_fetch', 'http_client', 'browser', 'web_search']);
|
|
89
|
+
// ── Scorer ──
|
|
90
|
+
class RiskScorer {
|
|
91
|
+
sessionHistory = [];
|
|
92
|
+
/**
|
|
93
|
+
* Assess risk for a tool call.
|
|
94
|
+
*
|
|
95
|
+
* @param toolName — name of the tool being invoked
|
|
96
|
+
* @param args — tool arguments
|
|
97
|
+
* @param permission — tool's effective permission level
|
|
98
|
+
*/
|
|
99
|
+
assess(toolName, args, permission = 'auto') {
|
|
100
|
+
try {
|
|
101
|
+
const factors = [
|
|
102
|
+
this.scorePermissionLevel(permission),
|
|
103
|
+
this.scoreFilePathSensitivity(toolName, args),
|
|
104
|
+
this.scoreCommandDestructiveness(toolName, args),
|
|
105
|
+
this.scoreNetworkAccess(toolName),
|
|
106
|
+
this.scoreDataVolume(args),
|
|
107
|
+
this.scoreCumulativeRisk(),
|
|
108
|
+
];
|
|
109
|
+
const score = Math.min(100, Math.round(factors.reduce((sum, f) => sum + f.weighted, 0)));
|
|
110
|
+
const level = scoreToLevel(score);
|
|
111
|
+
const assessment = { score, level, factors };
|
|
112
|
+
this.sessionHistory.push(assessment);
|
|
113
|
+
return assessment;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Fail-safe: return zero risk if scoring fails
|
|
117
|
+
return { score: 0, level: 'green', factors: [] };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Get all assessments from this session */
|
|
121
|
+
getHistory() {
|
|
122
|
+
return [...this.sessionHistory];
|
|
123
|
+
}
|
|
124
|
+
/** Get the session's cumulative average risk */
|
|
125
|
+
getSessionAverage() {
|
|
126
|
+
if (this.sessionHistory.length === 0)
|
|
127
|
+
return 0;
|
|
128
|
+
const sum = this.sessionHistory.reduce((s, a) => s + a.score, 0);
|
|
129
|
+
return Math.round(sum / this.sessionHistory.length);
|
|
130
|
+
}
|
|
131
|
+
/** Format a colored risk indicator for CLI display */
|
|
132
|
+
static formatIndicator(assessment) {
|
|
133
|
+
const { score, level } = assessment;
|
|
134
|
+
const colorMap = {
|
|
135
|
+
green: '\x1b[32m',
|
|
136
|
+
yellow: '\x1b[33m',
|
|
137
|
+
orange: '\x1b[38;5;208m',
|
|
138
|
+
red: '\x1b[31m',
|
|
139
|
+
};
|
|
140
|
+
const color = colorMap[level] || '';
|
|
141
|
+
const reset = '\x1b[0m';
|
|
142
|
+
return `${color}[Risk: ${score} ${level}]${reset}`;
|
|
143
|
+
}
|
|
144
|
+
// ── Factor Scorers ──
|
|
145
|
+
/** Factor 1: Tool permission level (weight 30) */
|
|
146
|
+
scorePermissionLevel(permission) {
|
|
147
|
+
const weight = 30;
|
|
148
|
+
let rawScore;
|
|
149
|
+
let reason;
|
|
150
|
+
switch (permission) {
|
|
151
|
+
case 'auto':
|
|
152
|
+
rawScore = 0;
|
|
153
|
+
reason = 'Auto-approved tool';
|
|
154
|
+
break;
|
|
155
|
+
case 'prompt':
|
|
156
|
+
rawScore = 50;
|
|
157
|
+
reason = 'Requires prompt approval';
|
|
158
|
+
break;
|
|
159
|
+
case 'always-ask':
|
|
160
|
+
rawScore = 100;
|
|
161
|
+
reason = 'Always requires explicit approval';
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
rawScore = 0;
|
|
165
|
+
reason = 'Unknown permission level';
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
name: 'permission_level',
|
|
169
|
+
weight,
|
|
170
|
+
rawScore,
|
|
171
|
+
weighted: rawScore * (weight / 100),
|
|
172
|
+
reason,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** Factor 2: File path sensitivity (weight 20) */
|
|
176
|
+
scoreFilePathSensitivity(toolName, args) {
|
|
177
|
+
const weight = 20;
|
|
178
|
+
const filePath = args.path || args.file || '';
|
|
179
|
+
if (!filePath) {
|
|
180
|
+
return { name: 'file_path', weight, rawScore: 0, weighted: 0, reason: 'No file path' };
|
|
181
|
+
}
|
|
182
|
+
// Check sensitive patterns
|
|
183
|
+
for (const pattern of SENSITIVE_PATH_PATTERNS) {
|
|
184
|
+
if (pattern.test(filePath)) {
|
|
185
|
+
return {
|
|
186
|
+
name: 'file_path',
|
|
187
|
+
weight,
|
|
188
|
+
rawScore: 100,
|
|
189
|
+
weighted: weight,
|
|
190
|
+
reason: `Sensitive path: ${filePath}`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Check moderate patterns
|
|
195
|
+
for (const pattern of MODERATE_PATH_PATTERNS) {
|
|
196
|
+
if (pattern.test(filePath)) {
|
|
197
|
+
return {
|
|
198
|
+
name: 'file_path',
|
|
199
|
+
weight,
|
|
200
|
+
rawScore: 40,
|
|
201
|
+
weighted: 40 * (weight / 100),
|
|
202
|
+
reason: `Config file: ${filePath}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Project source files — low risk
|
|
207
|
+
return {
|
|
208
|
+
name: 'file_path',
|
|
209
|
+
weight,
|
|
210
|
+
rawScore: 10,
|
|
211
|
+
weighted: 10 * (weight / 100),
|
|
212
|
+
reason: `Project file: ${filePath}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/** Factor 3: Command destructiveness (weight 20) */
|
|
216
|
+
scoreCommandDestructiveness(toolName, args) {
|
|
217
|
+
const weight = 20;
|
|
218
|
+
const command = args.command || '';
|
|
219
|
+
if (toolName !== 'execute' || !command) {
|
|
220
|
+
return { name: 'command', weight, rawScore: 0, weighted: 0, reason: 'Not a shell command' };
|
|
221
|
+
}
|
|
222
|
+
// Check destructive
|
|
223
|
+
for (const pattern of DESTRUCTIVE_COMMANDS) {
|
|
224
|
+
if (pattern.test(command)) {
|
|
225
|
+
return {
|
|
226
|
+
name: 'command',
|
|
227
|
+
weight,
|
|
228
|
+
rawScore: 100,
|
|
229
|
+
weighted: weight,
|
|
230
|
+
reason: `Destructive command: ${command.substring(0, 60)}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Check moderate
|
|
235
|
+
for (const pattern of MODERATE_COMMANDS) {
|
|
236
|
+
if (pattern.test(command)) {
|
|
237
|
+
return {
|
|
238
|
+
name: 'command',
|
|
239
|
+
weight,
|
|
240
|
+
rawScore: 50,
|
|
241
|
+
weighted: 50 * (weight / 100),
|
|
242
|
+
reason: `Moderate risk command: ${command.substring(0, 60)}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Check safe
|
|
247
|
+
for (const pattern of SAFE_COMMANDS) {
|
|
248
|
+
if (pattern.test(command)) {
|
|
249
|
+
return {
|
|
250
|
+
name: 'command',
|
|
251
|
+
weight,
|
|
252
|
+
rawScore: 5,
|
|
253
|
+
weighted: 5 * (weight / 100),
|
|
254
|
+
reason: `Safe command: ${command.substring(0, 60)}`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Unknown command — moderate risk
|
|
259
|
+
return {
|
|
260
|
+
name: 'command',
|
|
261
|
+
weight,
|
|
262
|
+
rawScore: 30,
|
|
263
|
+
weighted: 30 * (weight / 100),
|
|
264
|
+
reason: `Unknown command: ${command.substring(0, 60)}`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** Factor 4: Network access (weight 15) */
|
|
268
|
+
scoreNetworkAccess(toolName) {
|
|
269
|
+
const weight = 15;
|
|
270
|
+
if (NETWORK_TOOLS.has(toolName)) {
|
|
271
|
+
return {
|
|
272
|
+
name: 'network',
|
|
273
|
+
weight,
|
|
274
|
+
rawScore: 70,
|
|
275
|
+
weighted: 70 * (weight / 100),
|
|
276
|
+
reason: `Network-accessing tool: ${toolName}`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return { name: 'network', weight, rawScore: 0, weighted: 0, reason: 'No network access' };
|
|
280
|
+
}
|
|
281
|
+
/** Factor 5: Data volume (weight 10) */
|
|
282
|
+
scoreDataVolume(args) {
|
|
283
|
+
const weight = 10;
|
|
284
|
+
const content = args.content || args.body || '';
|
|
285
|
+
const command = args.command || '';
|
|
286
|
+
const totalSize = content.length + command.length + JSON.stringify(args).length;
|
|
287
|
+
// Check for pipes/redirects in commands
|
|
288
|
+
const hasPipe = /\|/.test(command);
|
|
289
|
+
const hasRedirect = />/.test(command);
|
|
290
|
+
if (totalSize > 10240) {
|
|
291
|
+
return {
|
|
292
|
+
name: 'data_volume',
|
|
293
|
+
weight,
|
|
294
|
+
rawScore: 90,
|
|
295
|
+
weighted: 90 * (weight / 100),
|
|
296
|
+
reason: `Large payload: ${(totalSize / 1024).toFixed(1)}KB`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (hasPipe || hasRedirect) {
|
|
300
|
+
return {
|
|
301
|
+
name: 'data_volume',
|
|
302
|
+
weight,
|
|
303
|
+
rawScore: 50,
|
|
304
|
+
weighted: 50 * (weight / 100),
|
|
305
|
+
reason: 'Command uses pipes/redirects',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (totalSize > 2048) {
|
|
309
|
+
return {
|
|
310
|
+
name: 'data_volume',
|
|
311
|
+
weight,
|
|
312
|
+
rawScore: 30,
|
|
313
|
+
weighted: 30 * (weight / 100),
|
|
314
|
+
reason: `Moderate payload: ${(totalSize / 1024).toFixed(1)}KB`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return { name: 'data_volume', weight, rawScore: 0, weighted: 0, reason: 'Small payload' };
|
|
318
|
+
}
|
|
319
|
+
/** Factor 6: Cumulative session risk (weight 5) */
|
|
320
|
+
scoreCumulativeRisk() {
|
|
321
|
+
const weight = 5;
|
|
322
|
+
const count = this.sessionHistory.length;
|
|
323
|
+
if (count === 0) {
|
|
324
|
+
return { name: 'cumulative', weight, rawScore: 0, weighted: 0, reason: 'First tool call' };
|
|
325
|
+
}
|
|
326
|
+
// Count high-risk calls in session
|
|
327
|
+
const highRisk = this.sessionHistory.filter(a => a.score > 50).length;
|
|
328
|
+
const ratio = highRisk / count;
|
|
329
|
+
if (ratio > 0.5) {
|
|
330
|
+
return {
|
|
331
|
+
name: 'cumulative',
|
|
332
|
+
weight,
|
|
333
|
+
rawScore: 80,
|
|
334
|
+
weighted: 80 * (weight / 100),
|
|
335
|
+
reason: `High-risk session: ${highRisk}/${count} calls above 50`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (count > 20) {
|
|
339
|
+
return {
|
|
340
|
+
name: 'cumulative',
|
|
341
|
+
weight,
|
|
342
|
+
rawScore: 40,
|
|
343
|
+
weighted: 40 * (weight / 100),
|
|
344
|
+
reason: `Long session: ${count} tool calls`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
name: 'cumulative',
|
|
349
|
+
weight,
|
|
350
|
+
rawScore: 10,
|
|
351
|
+
weighted: 10 * (weight / 100),
|
|
352
|
+
reason: `Session: ${count} tool calls`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports.RiskScorer = RiskScorer;
|
|
357
|
+
// ── Helpers ──
|
|
358
|
+
function scoreToLevel(score) {
|
|
359
|
+
if (score <= 25)
|
|
360
|
+
return 'green';
|
|
361
|
+
if (score <= 50)
|
|
362
|
+
return 'yellow';
|
|
363
|
+
if (score <= 75)
|
|
364
|
+
return 'orange';
|
|
365
|
+
return 'red';
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=risk.js.map
|
package/dist/sarif.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF 2.1.0 Export for CodeBot v1.9.0
|
|
3
|
+
*
|
|
4
|
+
* Converts AuditEntry[] to SARIF 2.1.0 JSON (Static Analysis Results
|
|
5
|
+
* Interchange Format). Only security-relevant entries become results;
|
|
6
|
+
* successful executes are excluded.
|
|
7
|
+
*
|
|
8
|
+
* Rule mapping:
|
|
9
|
+
* security_block → CB001 / error
|
|
10
|
+
* policy_block → CB002 / warning
|
|
11
|
+
* capability_block → CB003 / warning
|
|
12
|
+
* error → CB004 / note
|
|
13
|
+
* deny → CB005 / note
|
|
14
|
+
*
|
|
15
|
+
* Usage: codebot --export-audit sarif [session-id] > results.sarif
|
|
16
|
+
*
|
|
17
|
+
* NEVER throws — export failures must not crash the agent.
|
|
18
|
+
*/
|
|
19
|
+
import type { AuditEntry } from './audit';
|
|
20
|
+
export interface SarifLog {
|
|
21
|
+
$schema: string;
|
|
22
|
+
version: string;
|
|
23
|
+
runs: SarifRun[];
|
|
24
|
+
}
|
|
25
|
+
export interface SarifRun {
|
|
26
|
+
tool: {
|
|
27
|
+
driver: {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
informationUri: string;
|
|
31
|
+
rules: SarifRule[];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
results: SarifResult[];
|
|
35
|
+
invocations: SarifInvocation[];
|
|
36
|
+
}
|
|
37
|
+
export interface SarifRule {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
shortDescription: {
|
|
41
|
+
text: string;
|
|
42
|
+
};
|
|
43
|
+
defaultConfiguration: {
|
|
44
|
+
level: 'error' | 'warning' | 'note';
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface SarifResult {
|
|
48
|
+
ruleId: string;
|
|
49
|
+
level: 'error' | 'warning' | 'note';
|
|
50
|
+
message: {
|
|
51
|
+
text: string;
|
|
52
|
+
};
|
|
53
|
+
locations?: SarifLocation[];
|
|
54
|
+
properties?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
export interface SarifLocation {
|
|
57
|
+
physicalLocation: {
|
|
58
|
+
artifactLocation: {
|
|
59
|
+
uri: string;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface SarifInvocation {
|
|
64
|
+
executionSuccessful: boolean;
|
|
65
|
+
startTimeUtc?: string;
|
|
66
|
+
endTimeUtc?: string;
|
|
67
|
+
properties?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
export interface SarifExportOptions {
|
|
70
|
+
version?: string;
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
startTime?: string;
|
|
73
|
+
endTime?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert audit entries to SARIF 2.1.0 log.
|
|
77
|
+
* Only security-relevant entries (blocks, errors, denials) become results.
|
|
78
|
+
*/
|
|
79
|
+
export declare function exportSarif(entries: AuditEntry[], options?: SarifExportOptions): SarifLog;
|
|
80
|
+
/** Serialize SARIF log to formatted JSON string */
|
|
81
|
+
export declare function sarifToString(log: SarifLog): string;
|
|
82
|
+
//# sourceMappingURL=sarif.d.ts.map
|
package/dist/sarif.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SARIF 2.1.0 Export for CodeBot v1.9.0
|
|
4
|
+
*
|
|
5
|
+
* Converts AuditEntry[] to SARIF 2.1.0 JSON (Static Analysis Results
|
|
6
|
+
* Interchange Format). Only security-relevant entries become results;
|
|
7
|
+
* successful executes are excluded.
|
|
8
|
+
*
|
|
9
|
+
* Rule mapping:
|
|
10
|
+
* security_block → CB001 / error
|
|
11
|
+
* policy_block → CB002 / warning
|
|
12
|
+
* capability_block → CB003 / warning
|
|
13
|
+
* error → CB004 / note
|
|
14
|
+
* deny → CB005 / note
|
|
15
|
+
*
|
|
16
|
+
* Usage: codebot --export-audit sarif [session-id] > results.sarif
|
|
17
|
+
*
|
|
18
|
+
* NEVER throws — export failures must not crash the agent.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.exportSarif = exportSarif;
|
|
22
|
+
exports.sarifToString = sarifToString;
|
|
23
|
+
// ── Rule Definitions ──
|
|
24
|
+
const RULES = [
|
|
25
|
+
{
|
|
26
|
+
id: 'CB001',
|
|
27
|
+
name: 'SecurityBlock',
|
|
28
|
+
shortDescription: { text: 'A tool call was blocked for security reasons' },
|
|
29
|
+
defaultConfiguration: { level: 'error' },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'CB002',
|
|
33
|
+
name: 'PolicyBlock',
|
|
34
|
+
shortDescription: { text: 'A tool call was blocked by policy configuration' },
|
|
35
|
+
defaultConfiguration: { level: 'warning' },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'CB003',
|
|
39
|
+
name: 'CapabilityBlock',
|
|
40
|
+
shortDescription: { text: 'A tool call was blocked by capability restrictions' },
|
|
41
|
+
defaultConfiguration: { level: 'warning' },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'CB004',
|
|
45
|
+
name: 'ToolError',
|
|
46
|
+
shortDescription: { text: 'A tool call resulted in an error' },
|
|
47
|
+
defaultConfiguration: { level: 'note' },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'CB005',
|
|
51
|
+
name: 'PermissionDenied',
|
|
52
|
+
shortDescription: { text: 'A tool call was denied by the user' },
|
|
53
|
+
defaultConfiguration: { level: 'note' },
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
const ACTION_TO_RULE = {
|
|
57
|
+
security_block: { ruleId: 'CB001', level: 'error' },
|
|
58
|
+
policy_block: { ruleId: 'CB002', level: 'warning' },
|
|
59
|
+
capability_block: { ruleId: 'CB003', level: 'warning' },
|
|
60
|
+
error: { ruleId: 'CB004', level: 'note' },
|
|
61
|
+
deny: { ruleId: 'CB005', level: 'note' },
|
|
62
|
+
};
|
|
63
|
+
// ── Export Functions ──
|
|
64
|
+
/**
|
|
65
|
+
* Convert audit entries to SARIF 2.1.0 log.
|
|
66
|
+
* Only security-relevant entries (blocks, errors, denials) become results.
|
|
67
|
+
*/
|
|
68
|
+
function exportSarif(entries, options) {
|
|
69
|
+
try {
|
|
70
|
+
const version = options?.version || '1.9.0';
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const mapping = ACTION_TO_RULE[entry.action];
|
|
74
|
+
if (!mapping)
|
|
75
|
+
continue; // Skip 'execute' — only security-relevant entries
|
|
76
|
+
const result = {
|
|
77
|
+
ruleId: mapping.ruleId,
|
|
78
|
+
level: mapping.level,
|
|
79
|
+
message: {
|
|
80
|
+
text: buildMessage(entry),
|
|
81
|
+
},
|
|
82
|
+
properties: {
|
|
83
|
+
timestamp: entry.timestamp,
|
|
84
|
+
sessionId: entry.sessionId,
|
|
85
|
+
sequence: entry.sequence,
|
|
86
|
+
tool: entry.tool,
|
|
87
|
+
action: entry.action,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
// Add file location if available
|
|
91
|
+
const filePath = extractFilePath(entry);
|
|
92
|
+
if (filePath) {
|
|
93
|
+
result.locations = [{
|
|
94
|
+
physicalLocation: {
|
|
95
|
+
artifactLocation: { uri: filePath },
|
|
96
|
+
},
|
|
97
|
+
}];
|
|
98
|
+
}
|
|
99
|
+
results.push(result);
|
|
100
|
+
}
|
|
101
|
+
// Determine invocation times
|
|
102
|
+
let startTime = options?.startTime;
|
|
103
|
+
let endTime = options?.endTime;
|
|
104
|
+
if (!startTime && entries.length > 0) {
|
|
105
|
+
startTime = entries[0].timestamp;
|
|
106
|
+
}
|
|
107
|
+
if (!endTime && entries.length > 0) {
|
|
108
|
+
endTime = entries[entries.length - 1].timestamp;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json',
|
|
112
|
+
version: '2.1.0',
|
|
113
|
+
runs: [{
|
|
114
|
+
tool: {
|
|
115
|
+
driver: {
|
|
116
|
+
name: 'CodeBot',
|
|
117
|
+
version,
|
|
118
|
+
informationUri: 'https://github.com/zanderone1980/codebot-ai',
|
|
119
|
+
rules: RULES,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
results,
|
|
123
|
+
invocations: [{
|
|
124
|
+
executionSuccessful: results.every(r => r.level !== 'error'),
|
|
125
|
+
startTimeUtc: startTime,
|
|
126
|
+
endTimeUtc: endTime,
|
|
127
|
+
properties: options?.sessionId ? { sessionId: options.sessionId } : undefined,
|
|
128
|
+
}],
|
|
129
|
+
}],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Fail-safe: return minimal valid SARIF
|
|
134
|
+
return {
|
|
135
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json',
|
|
136
|
+
version: '2.1.0',
|
|
137
|
+
runs: [{
|
|
138
|
+
tool: {
|
|
139
|
+
driver: {
|
|
140
|
+
name: 'CodeBot',
|
|
141
|
+
version: options?.version || '1.9.0',
|
|
142
|
+
informationUri: 'https://github.com/zanderone1980/codebot-ai',
|
|
143
|
+
rules: RULES,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
results: [],
|
|
147
|
+
invocations: [{ executionSuccessful: true }],
|
|
148
|
+
}],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Serialize SARIF log to formatted JSON string */
|
|
153
|
+
function sarifToString(log) {
|
|
154
|
+
return JSON.stringify(log, null, 2);
|
|
155
|
+
}
|
|
156
|
+
// ── Helpers ──
|
|
157
|
+
function buildMessage(entry) {
|
|
158
|
+
const parts = [];
|
|
159
|
+
parts.push(`Tool '${entry.tool}' — ${entry.action.replace(/_/g, ' ')}`);
|
|
160
|
+
if (entry.reason) {
|
|
161
|
+
parts.push(`: ${entry.reason}`);
|
|
162
|
+
}
|
|
163
|
+
return parts.join('');
|
|
164
|
+
}
|
|
165
|
+
function extractFilePath(entry) {
|
|
166
|
+
const path = entry.args?.path;
|
|
167
|
+
if (typeof path === 'string' && path.length > 0) {
|
|
168
|
+
return path;
|
|
169
|
+
}
|
|
170
|
+
const file = entry.args?.file;
|
|
171
|
+
if (typeof file === 'string' && file.length > 0) {
|
|
172
|
+
return file;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=sarif.js.map
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebot-ai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Zero-dependency autonomous AI agent. Code, browse, search, automate. Works with any LLM — Ollama, Claude, GPT, Gemini, DeepSeek, Groq, Mistral, Grok.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|