codebot-ai 1.8.0 → 1.9.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/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
@@ -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
@@ -71,6 +71,10 @@ export interface AgentEvent {
71
71
  };
72
72
  error?: string;
73
73
  usage?: UsageStats;
74
+ risk?: {
75
+ score: number;
76
+ level: string;
77
+ };
74
78
  }
75
79
  export interface Config {
76
80
  provider: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebot-ai",
3
- "version": "1.8.0",
3
+ "version": "1.9.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",