codebot-ai 1.6.0 → 1.8.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/history.js CHANGED
@@ -38,38 +38,50 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const os = __importStar(require("os"));
40
40
  const crypto = __importStar(require("crypto"));
41
+ const integrity_1 = require("./integrity");
41
42
  const SESSIONS_DIR = path.join(os.homedir(), '.codebot', 'sessions');
42
43
  class SessionManager {
43
44
  sessionId;
44
45
  filePath;
45
46
  model;
47
+ integrityKey;
46
48
  constructor(model, sessionId) {
47
49
  this.model = model;
48
50
  this.sessionId = sessionId || crypto.randomUUID();
51
+ this.integrityKey = (0, integrity_1.deriveSessionKey)(this.sessionId);
49
52
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
50
53
  this.filePath = path.join(SESSIONS_DIR, `${this.sessionId}.jsonl`);
51
54
  }
52
55
  getId() {
53
56
  return this.sessionId;
54
57
  }
55
- /** Append a message to the session file */
58
+ /** Append a message to the session file (HMAC signed) */
56
59
  save(message) {
57
60
  try {
58
- const line = JSON.stringify({
61
+ const record = {
59
62
  ...message,
60
63
  _ts: new Date().toISOString(),
61
64
  _model: this.model,
62
- });
63
- fs.appendFileSync(this.filePath, line + '\n');
65
+ };
66
+ record._sig = (0, integrity_1.signMessage)(record, this.integrityKey);
67
+ fs.appendFileSync(this.filePath, JSON.stringify(record) + '\n');
64
68
  }
65
69
  catch {
66
70
  // Don't crash on write failure — session persistence is best-effort
67
71
  }
68
72
  }
69
- /** Save all messages (atomic overwrite via temp file + rename) */
73
+ /** Save all messages (atomic overwrite, HMAC signed) */
70
74
  saveAll(messages) {
71
75
  try {
72
- const lines = messages.map(m => JSON.stringify({ ...m, _ts: new Date().toISOString(), _model: this.model }));
76
+ const lines = messages.map(m => {
77
+ const record = {
78
+ ...m,
79
+ _ts: new Date().toISOString(),
80
+ _model: this.model,
81
+ };
82
+ record._sig = (0, integrity_1.signMessage)(record, this.integrityKey);
83
+ return JSON.stringify(record);
84
+ });
73
85
  const tmpPath = this.filePath + '.tmp';
74
86
  fs.writeFileSync(tmpPath, lines.join('\n') + '\n');
75
87
  fs.renameSync(tmpPath, this.filePath);
@@ -78,7 +90,7 @@ class SessionManager {
78
90
  // Don't crash — session persistence is best-effort
79
91
  }
80
92
  }
81
- /** Load messages from a session file (skips malformed lines) */
93
+ /** Load messages from a session file (verifies HMAC, drops tampered) */
82
94
  load() {
83
95
  if (!fs.existsSync(this.filePath))
84
96
  return [];
@@ -92,20 +104,55 @@ class SessionManager {
92
104
  if (!content)
93
105
  return [];
94
106
  const messages = [];
107
+ let dropped = 0;
95
108
  for (const line of content.split('\n')) {
96
109
  try {
97
110
  const obj = JSON.parse(line);
111
+ // Verify integrity if signature present (backward compat: unsigned messages pass)
112
+ if (obj._sig) {
113
+ if (!(0, integrity_1.verifyMessage)(obj, this.integrityKey)) {
114
+ dropped++;
115
+ continue; // Drop tampered message
116
+ }
117
+ }
98
118
  delete obj._ts;
99
119
  delete obj._model;
120
+ delete obj._sig;
100
121
  messages.push(obj);
101
122
  }
102
123
  catch {
103
- // Skip malformed line — don't crash the whole load
104
124
  continue;
105
125
  }
106
126
  }
127
+ if (dropped > 0) {
128
+ console.warn(`Warning: Dropped ${dropped} tampered message(s) from session ${this.sessionId.substring(0, 8)}.`);
129
+ }
107
130
  return messages;
108
131
  }
132
+ /** Verify integrity of all messages in this session. */
133
+ verifyIntegrity() {
134
+ if (!fs.existsSync(this.filePath)) {
135
+ return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
136
+ }
137
+ try {
138
+ const content = fs.readFileSync(this.filePath, 'utf-8').trim();
139
+ if (!content)
140
+ return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
141
+ const records = [];
142
+ for (const line of content.split('\n')) {
143
+ try {
144
+ records.push(JSON.parse(line));
145
+ }
146
+ catch {
147
+ continue;
148
+ }
149
+ }
150
+ return (0, integrity_1.verifyMessages)(records, this.integrityKey);
151
+ }
152
+ catch {
153
+ return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
154
+ }
155
+ }
109
156
  /** List recent sessions */
110
157
  static list(limit = 10) {
111
158
  if (!fs.existsSync(SESSIONS_DIR))
package/dist/index.d.ts CHANGED
@@ -11,5 +11,11 @@ export { loadPlugins } from './plugins';
11
11
  export { loadMCPTools } from './mcp';
12
12
  export { MODEL_REGISTRY, PROVIDER_DEFAULTS, getModelInfo, detectProvider } from './providers/registry';
13
13
  export type { ModelInfo } from './providers/registry';
14
+ export { CapabilityChecker } from './capabilities';
15
+ export type { ToolCapabilities, CapabilityConfig } from './capabilities';
16
+ export { deriveSessionKey, signMessage, verifyMessage, verifyMessages } from './integrity';
17
+ export type { IntegrityResult } from './integrity';
18
+ export { ReplayProvider, loadSessionForReplay, compareOutputs, listReplayableSessions } from './replay';
19
+ export type { SessionReplayData, ReplayDivergence } from './replay';
14
20
  export * from './types';
15
21
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
17
+ exports.listReplayableSessions = exports.compareOutputs = exports.loadSessionForReplay = exports.ReplayProvider = exports.verifyMessages = exports.verifyMessage = exports.signMessage = exports.deriveSessionKey = exports.CapabilityChecker = exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
18
18
  var agent_1 = require("./agent");
19
19
  Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_1.Agent; } });
20
20
  var openai_1 = require("./providers/openai");
@@ -42,5 +42,17 @@ Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: functi
42
42
  Object.defineProperty(exports, "PROVIDER_DEFAULTS", { enumerable: true, get: function () { return registry_1.PROVIDER_DEFAULTS; } });
43
43
  Object.defineProperty(exports, "getModelInfo", { enumerable: true, get: function () { return registry_1.getModelInfo; } });
44
44
  Object.defineProperty(exports, "detectProvider", { enumerable: true, get: function () { return registry_1.detectProvider; } });
45
+ var capabilities_1 = require("./capabilities");
46
+ Object.defineProperty(exports, "CapabilityChecker", { enumerable: true, get: function () { return capabilities_1.CapabilityChecker; } });
47
+ var integrity_1 = require("./integrity");
48
+ Object.defineProperty(exports, "deriveSessionKey", { enumerable: true, get: function () { return integrity_1.deriveSessionKey; } });
49
+ Object.defineProperty(exports, "signMessage", { enumerable: true, get: function () { return integrity_1.signMessage; } });
50
+ Object.defineProperty(exports, "verifyMessage", { enumerable: true, get: function () { return integrity_1.verifyMessage; } });
51
+ Object.defineProperty(exports, "verifyMessages", { enumerable: true, get: function () { return integrity_1.verifyMessages; } });
52
+ var replay_1 = require("./replay");
53
+ Object.defineProperty(exports, "ReplayProvider", { enumerable: true, get: function () { return replay_1.ReplayProvider; } });
54
+ Object.defineProperty(exports, "loadSessionForReplay", { enumerable: true, get: function () { return replay_1.loadSessionForReplay; } });
55
+ Object.defineProperty(exports, "compareOutputs", { enumerable: true, get: function () { return replay_1.compareOutputs; } });
56
+ Object.defineProperty(exports, "listReplayableSessions", { enumerable: true, get: function () { return replay_1.listReplayableSessions; } });
45
57
  __exportStar(require("./types"), exports);
46
58
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Session History Integrity for CodeBot v1.8.0
3
+ *
4
+ * HMAC-SHA256 signing/verification for session messages.
5
+ * Key: SHA-256(sessionId + machineId) where machineId = hostname:username.
6
+ *
7
+ * This is tamper-DETECTION, not tamper-prevention. The key is deterministic
8
+ * per session+machine so verification works without storing keys separately.
9
+ *
10
+ * Uses Node's built-in crypto module — zero runtime dependencies.
11
+ */
12
+ /** Derive a per-session HMAC key. Deterministic for same session+machine. */
13
+ export declare function deriveSessionKey(sessionId: string): Buffer;
14
+ /**
15
+ * Compute HMAC-SHA256 signature for a message object.
16
+ * Excludes the _sig field from the signing input.
17
+ */
18
+ export declare function signMessage(message: Record<string, unknown>, key: Buffer): string;
19
+ /**
20
+ * Verify a message's HMAC signature.
21
+ * Returns false if no signature present or if it doesn't match.
22
+ */
23
+ export declare function verifyMessage(message: Record<string, unknown>, key: Buffer): boolean;
24
+ export interface IntegrityResult {
25
+ valid: number;
26
+ tampered: number;
27
+ unsigned: number;
28
+ tamperedIndices: number[];
29
+ }
30
+ /**
31
+ * Verify an array of signed messages.
32
+ * Unsigned messages (pre-v1.8.0) are counted but not flagged as tampered.
33
+ */
34
+ export declare function verifyMessages(messages: Array<Record<string, unknown>>, key: Buffer): IntegrityResult;
35
+ //# sourceMappingURL=integrity.d.ts.map
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Session History Integrity for CodeBot v1.8.0
4
+ *
5
+ * HMAC-SHA256 signing/verification for session messages.
6
+ * Key: SHA-256(sessionId + machineId) where machineId = hostname:username.
7
+ *
8
+ * This is tamper-DETECTION, not tamper-prevention. The key is deterministic
9
+ * per session+machine so verification works without storing keys separately.
10
+ *
11
+ * Uses Node's built-in crypto module — zero runtime dependencies.
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.deriveSessionKey = deriveSessionKey;
48
+ exports.signMessage = signMessage;
49
+ exports.verifyMessage = verifyMessage;
50
+ exports.verifyMessages = verifyMessages;
51
+ const crypto = __importStar(require("crypto"));
52
+ const os = __importStar(require("os"));
53
+ // ── Key Derivation ──
54
+ /** Derive a per-session HMAC key. Deterministic for same session+machine. */
55
+ function deriveSessionKey(sessionId) {
56
+ const machineId = os.hostname() + ':' + getUserName();
57
+ return crypto.createHash('sha256')
58
+ .update(sessionId + machineId)
59
+ .digest();
60
+ }
61
+ /** Get username safely. */
62
+ function getUserName() {
63
+ try {
64
+ return os.userInfo().username;
65
+ }
66
+ catch {
67
+ return 'unknown';
68
+ }
69
+ }
70
+ // ── Signing & Verification ──
71
+ /**
72
+ * Compute HMAC-SHA256 signature for a message object.
73
+ * Excludes the _sig field from the signing input.
74
+ */
75
+ function signMessage(message, key) {
76
+ const payload = canonicalize(message);
77
+ return crypto.createHmac('sha256', key)
78
+ .update(payload)
79
+ .digest('hex');
80
+ }
81
+ /**
82
+ * Verify a message's HMAC signature.
83
+ * Returns false if no signature present or if it doesn't match.
84
+ */
85
+ function verifyMessage(message, key) {
86
+ const sig = message._sig;
87
+ if (!sig)
88
+ return false;
89
+ const expected = signMessage(message, key);
90
+ try {
91
+ return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
92
+ }
93
+ catch {
94
+ return false; // Different lengths or invalid hex
95
+ }
96
+ }
97
+ /**
98
+ * Canonical JSON representation for signing.
99
+ * Excludes _sig field. Keys are sorted for determinism.
100
+ */
101
+ function canonicalize(obj) {
102
+ const sorted = {};
103
+ for (const key of Object.keys(obj).sort()) {
104
+ if (key === '_sig')
105
+ continue;
106
+ sorted[key] = obj[key];
107
+ }
108
+ return JSON.stringify(sorted);
109
+ }
110
+ /**
111
+ * Verify an array of signed messages.
112
+ * Unsigned messages (pre-v1.8.0) are counted but not flagged as tampered.
113
+ */
114
+ function verifyMessages(messages, key) {
115
+ let valid = 0;
116
+ let tampered = 0;
117
+ let unsigned = 0;
118
+ const tamperedIndices = [];
119
+ for (let i = 0; i < messages.length; i++) {
120
+ const msg = messages[i];
121
+ if (!msg._sig) {
122
+ unsigned++;
123
+ continue;
124
+ }
125
+ if (verifyMessage(msg, key)) {
126
+ valid++;
127
+ }
128
+ else {
129
+ tampered++;
130
+ tamperedIndices.push(i);
131
+ }
132
+ }
133
+ return { valid, tampered, unsigned, tamperedIndices };
134
+ }
135
+ //# sourceMappingURL=integrity.js.map
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Policy Engine for CodeBot v1.7.0
3
+ *
4
+ * Loads, validates, and enforces declarative security policies.
5
+ * Policy files: .codebot/policy.json (project) + ~/.codebot/policy.json (global)
6
+ * Project policy overrides global policy where specified.
7
+ */
8
+ import { ToolCapabilities } from './capabilities';
9
+ export interface PolicyExecution {
10
+ sandbox?: 'docker' | 'host' | 'auto';
11
+ network?: boolean;
12
+ timeout_seconds?: number;
13
+ max_memory_mb?: number;
14
+ }
15
+ export interface PolicyFilesystem {
16
+ writable_paths?: string[];
17
+ read_only_paths?: string[];
18
+ denied_paths?: string[];
19
+ allow_outside_project?: boolean;
20
+ }
21
+ export interface PolicyToolPermission {
22
+ [toolName: string]: 'auto' | 'prompt' | 'always-ask';
23
+ }
24
+ export interface PolicyTools {
25
+ enabled?: string[];
26
+ disabled?: string[];
27
+ permissions?: PolicyToolPermission;
28
+ capabilities?: Record<string, ToolCapabilities>;
29
+ }
30
+ export interface PolicySecrets {
31
+ block_on_detect?: boolean;
32
+ scan_on_write?: boolean;
33
+ allowed_patterns?: string[];
34
+ }
35
+ export interface PolicyGit {
36
+ always_branch?: boolean;
37
+ branch_prefix?: string;
38
+ require_tests_before_commit?: boolean;
39
+ never_push_main?: boolean;
40
+ }
41
+ export interface PolicyMcp {
42
+ allowed_servers?: string[];
43
+ blocked_servers?: string[];
44
+ }
45
+ export interface PolicyLimits {
46
+ max_iterations?: number;
47
+ max_file_size_kb?: number;
48
+ max_files_per_operation?: number;
49
+ cost_limit_usd?: number;
50
+ }
51
+ export interface Policy {
52
+ version?: string;
53
+ execution?: PolicyExecution;
54
+ filesystem?: PolicyFilesystem;
55
+ tools?: PolicyTools;
56
+ secrets?: PolicySecrets;
57
+ git?: PolicyGit;
58
+ mcp?: PolicyMcp;
59
+ limits?: PolicyLimits;
60
+ }
61
+ export declare const DEFAULT_POLICY: Required<Policy>;
62
+ /**
63
+ * Load and merge policies from project + global locations.
64
+ * Project policy overrides global where specified.
65
+ */
66
+ export declare function loadPolicy(projectRoot?: string): Policy;
67
+ export declare class PolicyEnforcer {
68
+ private policy;
69
+ private projectRoot;
70
+ constructor(policy?: Policy, projectRoot?: string);
71
+ getPolicy(): Policy;
72
+ /** Check if a tool is enabled by policy. Returns { allowed, reason }. */
73
+ isToolAllowed(toolName: string): {
74
+ allowed: boolean;
75
+ reason?: string;
76
+ };
77
+ /** Get the permission level for a tool (policy override or null for default). */
78
+ getToolPermission(toolName: string): 'auto' | 'prompt' | 'always-ask' | null;
79
+ /** Check if a path is writable according to policy. */
80
+ isPathWritable(filePath: string): {
81
+ allowed: boolean;
82
+ reason?: string;
83
+ };
84
+ /** Get sandbox mode. */
85
+ getSandboxMode(): 'docker' | 'host' | 'auto';
86
+ /** Check if network is allowed for executed commands. */
87
+ isNetworkAllowed(): boolean;
88
+ /** Get execution timeout in milliseconds. */
89
+ getTimeoutMs(): number;
90
+ /** Get max memory in MB for sandbox. */
91
+ getMaxMemoryMb(): number;
92
+ /** Check if agent should always work on a branch. */
93
+ shouldAlwaysBranch(): boolean;
94
+ /** Get branch prefix for auto-created branches. */
95
+ getBranchPrefix(): string;
96
+ /** Check if pushing to main/master is blocked. */
97
+ isMainPushBlocked(): boolean;
98
+ /** Should secrets block writes (vs just warn)? */
99
+ shouldBlockSecrets(): boolean;
100
+ /** Should scan for secrets on write? */
101
+ shouldScanSecrets(): boolean;
102
+ /** Check if an MCP server is allowed. */
103
+ isMcpServerAllowed(serverName: string): {
104
+ allowed: boolean;
105
+ reason?: string;
106
+ };
107
+ /** Get max iterations for the agent loop. */
108
+ getMaxIterations(): number;
109
+ /** Get max file size in bytes for write operations. */
110
+ getMaxFileSizeBytes(): number;
111
+ /** Get cost limit in USD (0 = no limit). */
112
+ getCostLimitUsd(): number;
113
+ /** Get capability restrictions for a tool. undefined = unrestricted. */
114
+ getToolCapabilities(toolName: string): ToolCapabilities | undefined;
115
+ /** Check a specific capability for a tool. Returns { allowed, reason }. */
116
+ checkCapability(toolName: string, capabilityType: keyof ToolCapabilities, value: string | number): {
117
+ allowed: boolean;
118
+ reason?: string;
119
+ };
120
+ /**
121
+ * Simple glob-like pattern matching:
122
+ * - `*` matches any single path component
123
+ * - `**` matches any number of path components
124
+ * - `.env` matches exact filename
125
+ */
126
+ private matchesPattern;
127
+ }
128
+ /**
129
+ * Generate a default policy file content for `codebot --init-policy`.
130
+ */
131
+ export declare function generateDefaultPolicyFile(): string;
132
+ //# sourceMappingURL=policy.d.ts.map