codebot-ai 1.7.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/agent.d.ts +18 -0
- package/dist/agent.js +135 -6
- package/dist/audit.d.ts +1 -1
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +187 -0
- package/dist/cli.js +167 -4
- package/dist/history.d.ts +7 -3
- package/dist/history.js +55 -8
- package/dist/index.d.ts +12 -0
- package/dist/index.js +20 -1
- package/dist/integrity.d.ts +35 -0
- package/dist/integrity.js +135 -0
- package/dist/metrics.d.ts +60 -0
- package/dist/metrics.js +296 -0
- package/dist/policy.d.ts +9 -0
- package/dist/policy.js +32 -6
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +4 -0
- package/dist/providers/openai.d.ts +1 -0
- package/dist/providers/openai.js +4 -0
- package/dist/replay.d.ts +55 -0
- package/dist/replay.js +196 -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/tools/batch-edit.d.ts +3 -0
- package/dist/tools/batch-edit.js +12 -0
- package/dist/tools/edit.d.ts +3 -0
- package/dist/tools/edit.js +11 -0
- package/dist/tools/git.d.ts +5 -0
- package/dist/tools/git.js +31 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +6 -6
- package/dist/tools/write.d.ts +3 -0
- package/dist/tools/write.js +11 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/replay.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session Replay Engine for CodeBot v1.8.0
|
|
4
|
+
*
|
|
5
|
+
* Replays saved sessions by feeding recorded assistant responses
|
|
6
|
+
* instead of calling the LLM. Tool calls are re-executed and outputs
|
|
7
|
+
* compared against recorded results to detect environment divergences.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* codebot --replay <session-id>
|
|
11
|
+
* codebot --replay (replays latest session)
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.ReplayProvider = void 0;
|
|
48
|
+
exports.loadSessionForReplay = loadSessionForReplay;
|
|
49
|
+
exports.compareOutputs = compareOutputs;
|
|
50
|
+
exports.listReplayableSessions = listReplayableSessions;
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const os = __importStar(require("os"));
|
|
54
|
+
// ── Replay Provider ──
|
|
55
|
+
/**
|
|
56
|
+
* Mock LLM provider that feeds recorded assistant messages.
|
|
57
|
+
* Used during replay to bypass actual LLM calls.
|
|
58
|
+
*/
|
|
59
|
+
class ReplayProvider {
|
|
60
|
+
name = 'replay';
|
|
61
|
+
assistantMessages;
|
|
62
|
+
callIndex = 0;
|
|
63
|
+
constructor(assistantMessages) {
|
|
64
|
+
this.assistantMessages = assistantMessages;
|
|
65
|
+
}
|
|
66
|
+
async *chat(_messages, _tools) {
|
|
67
|
+
const msg = this.assistantMessages[this.callIndex++];
|
|
68
|
+
if (!msg) {
|
|
69
|
+
yield { type: 'text', text: '[replay: no more recorded responses]' };
|
|
70
|
+
yield { type: 'done' };
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Emit text content
|
|
74
|
+
if (msg.content) {
|
|
75
|
+
yield { type: 'text', text: msg.content };
|
|
76
|
+
}
|
|
77
|
+
// Emit tool calls
|
|
78
|
+
if (msg.tool_calls) {
|
|
79
|
+
for (const tc of msg.tool_calls) {
|
|
80
|
+
yield {
|
|
81
|
+
type: 'tool_call_end',
|
|
82
|
+
toolCall: tc,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
yield { type: 'done' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.ReplayProvider = ReplayProvider;
|
|
90
|
+
/**
|
|
91
|
+
* Load a session from disk and prepare it for replay.
|
|
92
|
+
* Returns null if session doesn't exist or is empty.
|
|
93
|
+
*/
|
|
94
|
+
function loadSessionForReplay(sessionId) {
|
|
95
|
+
const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
|
|
96
|
+
const filePath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
97
|
+
if (!fs.existsSync(filePath))
|
|
98
|
+
return null;
|
|
99
|
+
let content;
|
|
100
|
+
try {
|
|
101
|
+
content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (!content)
|
|
107
|
+
return null;
|
|
108
|
+
const messages = [];
|
|
109
|
+
for (const line of content.split('\n')) {
|
|
110
|
+
try {
|
|
111
|
+
const obj = JSON.parse(line);
|
|
112
|
+
delete obj._ts;
|
|
113
|
+
delete obj._model;
|
|
114
|
+
delete obj._sig;
|
|
115
|
+
messages.push(obj);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (messages.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
124
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
125
|
+
const toolResults = new Map();
|
|
126
|
+
for (const msg of messages) {
|
|
127
|
+
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
128
|
+
toolResults.set(msg.tool_call_id, msg.content);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { messages, assistantMessages, userMessages, toolResults };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Compare recorded vs actual tool output.
|
|
135
|
+
* Returns null if identical, or a diff description.
|
|
136
|
+
*/
|
|
137
|
+
function compareOutputs(recorded, actual) {
|
|
138
|
+
if (recorded === actual)
|
|
139
|
+
return null;
|
|
140
|
+
// Normalize whitespace for soft comparison
|
|
141
|
+
const normRecorded = recorded.trim().replace(/\s+/g, ' ');
|
|
142
|
+
const normActual = actual.trim().replace(/\s+/g, ' ');
|
|
143
|
+
if (normRecorded === normActual)
|
|
144
|
+
return null;
|
|
145
|
+
const maxShow = 200;
|
|
146
|
+
const expectedSnippet = normRecorded.length > maxShow
|
|
147
|
+
? normRecorded.substring(0, maxShow) + '...'
|
|
148
|
+
: normRecorded;
|
|
149
|
+
const actualSnippet = normActual.length > maxShow
|
|
150
|
+
? normActual.substring(0, maxShow) + '...'
|
|
151
|
+
: normActual;
|
|
152
|
+
return `Expected: ${expectedSnippet}\nActual: ${actualSnippet}`;
|
|
153
|
+
}
|
|
154
|
+
// ── Session Listing ──
|
|
155
|
+
/**
|
|
156
|
+
* List sessions available for replay.
|
|
157
|
+
*/
|
|
158
|
+
function listReplayableSessions(limit = 10) {
|
|
159
|
+
const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
|
|
160
|
+
if (!fs.existsSync(sessionsDir))
|
|
161
|
+
return [];
|
|
162
|
+
const files = fs.readdirSync(sessionsDir)
|
|
163
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
164
|
+
.map(f => {
|
|
165
|
+
const stat = fs.statSync(path.join(sessionsDir, f));
|
|
166
|
+
return { name: f, mtime: stat.mtime };
|
|
167
|
+
})
|
|
168
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
|
169
|
+
.slice(0, limit);
|
|
170
|
+
return files.map(f => {
|
|
171
|
+
const id = f.name.replace('.jsonl', '');
|
|
172
|
+
const fullPath = path.join(sessionsDir, f.name);
|
|
173
|
+
try {
|
|
174
|
+
const content = fs.readFileSync(fullPath, 'utf-8').trim();
|
|
175
|
+
const lines = content ? content.split('\n') : [];
|
|
176
|
+
let preview = '';
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
try {
|
|
179
|
+
const msg = JSON.parse(line);
|
|
180
|
+
if (msg.role === 'user') {
|
|
181
|
+
preview = msg.content.substring(0, 80);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { id, preview, messageCount: lines.length, date: f.mtime.toISOString() };
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return { id, preview: '', messageCount: 0, date: f.mtime.toISOString() };
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=replay.js.map
|
package/dist/risk.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RiskScorer for CodeBot v1.9.0
|
|
3
|
+
*
|
|
4
|
+
* Per-tool-call risk assessment using 6-factor weighted scoring (0–100).
|
|
5
|
+
* Factors: tool permission, file path sensitivity, command destructiveness,
|
|
6
|
+
* network access, data volume, cumulative session risk.
|
|
7
|
+
*
|
|
8
|
+
* Levels: green (0–25), yellow (26–50), orange (51–75), red (76+).
|
|
9
|
+
* NEVER throws — risk scoring failures must not crash the agent.
|
|
10
|
+
*/
|
|
11
|
+
export interface RiskAssessment {
|
|
12
|
+
score: number;
|
|
13
|
+
level: 'green' | 'yellow' | 'orange' | 'red';
|
|
14
|
+
factors: RiskFactor[];
|
|
15
|
+
}
|
|
16
|
+
export interface RiskFactor {
|
|
17
|
+
name: string;
|
|
18
|
+
weight: number;
|
|
19
|
+
rawScore: number;
|
|
20
|
+
weighted: number;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class RiskScorer {
|
|
24
|
+
private sessionHistory;
|
|
25
|
+
/**
|
|
26
|
+
* Assess risk for a tool call.
|
|
27
|
+
*
|
|
28
|
+
* @param toolName — name of the tool being invoked
|
|
29
|
+
* @param args — tool arguments
|
|
30
|
+
* @param permission — tool's effective permission level
|
|
31
|
+
*/
|
|
32
|
+
assess(toolName: string, args: Record<string, unknown>, permission?: 'auto' | 'prompt' | 'always-ask'): RiskAssessment;
|
|
33
|
+
/** Get all assessments from this session */
|
|
34
|
+
getHistory(): RiskAssessment[];
|
|
35
|
+
/** Get the session's cumulative average risk */
|
|
36
|
+
getSessionAverage(): number;
|
|
37
|
+
/** Format a colored risk indicator for CLI display */
|
|
38
|
+
static formatIndicator(assessment: RiskAssessment): string;
|
|
39
|
+
/** Factor 1: Tool permission level (weight 30) */
|
|
40
|
+
private scorePermissionLevel;
|
|
41
|
+
/** Factor 2: File path sensitivity (weight 20) */
|
|
42
|
+
private scoreFilePathSensitivity;
|
|
43
|
+
/** Factor 3: Command destructiveness (weight 20) */
|
|
44
|
+
private scoreCommandDestructiveness;
|
|
45
|
+
/** Factor 4: Network access (weight 15) */
|
|
46
|
+
private scoreNetworkAccess;
|
|
47
|
+
/** Factor 5: Data volume (weight 10) */
|
|
48
|
+
private scoreDataVolume;
|
|
49
|
+
/** Factor 6: Cumulative session risk (weight 5) */
|
|
50
|
+
private scoreCumulativeRisk;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=risk.d.ts.map
|
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
|