cli4ai 1.0.2 → 1.0.4
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/package.json +1 -1
- package/src/cli.ts +10 -0
- package/src/commands/add.ts +2 -25
- package/src/commands/run.ts +19 -2
- package/src/core/config.ts +15 -0
- package/src/core/execute.ts +60 -22
- package/src/core/lockfile.ts +2 -125
- package/src/mcp/server.ts +8 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -94,6 +94,8 @@ export function createProgram(): Command {
|
|
|
94
94
|
.command('run <package> [command] [args...]')
|
|
95
95
|
.description('Run a tool command')
|
|
96
96
|
.option('-e, --env <vars...>', 'Environment variables (KEY=value)')
|
|
97
|
+
.option('--scope <level>', 'Permission scope: read, write, or full (default: full)')
|
|
98
|
+
.option('--sandbox', 'Run in sandboxed environment with restricted file system access')
|
|
97
99
|
// Allow passing tool flags through (e.g. `cli4ai run chrome screenshot --full-page`)
|
|
98
100
|
.allowUnknownOption(true)
|
|
99
101
|
.addHelpText('after', `
|
|
@@ -101,10 +103,18 @@ export function createProgram(): Command {
|
|
|
101
103
|
Examples:
|
|
102
104
|
cli4ai run github trending
|
|
103
105
|
cli4ai run chrome screenshot https://example.com --full-page
|
|
106
|
+
cli4ai run github list-issues --scope read
|
|
107
|
+
cli4ai run untrusted-pkg process --sandbox
|
|
104
108
|
|
|
105
109
|
Pass-through:
|
|
106
110
|
Use "--" to pass flags that would otherwise be parsed by cli4ai:
|
|
107
111
|
cli4ai run <pkg> <cmd> -- --help
|
|
112
|
+
|
|
113
|
+
Security:
|
|
114
|
+
--scope read Only allow read operations (no mutations)
|
|
115
|
+
--scope write Allow write operations
|
|
116
|
+
--scope full Full access (default)
|
|
117
|
+
--sandbox Restrict file system access to temp directories
|
|
108
118
|
`)
|
|
109
119
|
.action(withErrorHandling(runCommand));
|
|
110
120
|
|
package/src/commands/add.ts
CHANGED
|
@@ -16,9 +16,8 @@ import {
|
|
|
16
16
|
LOCAL_PACKAGES_DIR,
|
|
17
17
|
loadConfig
|
|
18
18
|
} from '../core/config.js';
|
|
19
|
-
import { lockPackage,
|
|
19
|
+
import { lockPackage, type LockedPackage } from '../core/lockfile.js';
|
|
20
20
|
import { linkPackageDirect, isBinInPath, getPathInstructions } from '../core/link.js';
|
|
21
|
-
import { getRegistryIntegrity, verifyPackageIntegrity } from '../core/registry.js';
|
|
22
21
|
|
|
23
22
|
interface AddOptions {
|
|
24
23
|
local?: boolean;
|
|
@@ -417,27 +416,6 @@ export async function addCommand(packages: string[], options: AddOptions): Promi
|
|
|
417
416
|
await installNpmDependencies(result.path, plan.manifest.dependencies);
|
|
418
417
|
}
|
|
419
418
|
|
|
420
|
-
// SECURITY: Verify integrity against registry
|
|
421
|
-
let integrity: string | undefined;
|
|
422
|
-
try {
|
|
423
|
-
const verification = await verifyPackageIntegrity(result.name, result.path);
|
|
424
|
-
integrity = verification.actual;
|
|
425
|
-
|
|
426
|
-
if (verification.expected) {
|
|
427
|
-
if (verification.valid) {
|
|
428
|
-
log(` integrity: ${integrity.slice(0, 20)}... ✓`);
|
|
429
|
-
} else {
|
|
430
|
-
log(` ⚠️ Integrity mismatch!`);
|
|
431
|
-
log(` Expected: ${verification.expected.slice(0, 30)}...`);
|
|
432
|
-
log(` Actual: ${verification.actual.slice(0, 30)}...`);
|
|
433
|
-
}
|
|
434
|
-
} else {
|
|
435
|
-
log(` integrity: ${integrity.slice(0, 20)}...`);
|
|
436
|
-
}
|
|
437
|
-
} catch (err) {
|
|
438
|
-
log(` warning: could not compute integrity hash`);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
419
|
// Link to PATH for global installs
|
|
442
420
|
if (options.global) {
|
|
443
421
|
result.binPath = linkPackageDirect(plan.manifest, result.path);
|
|
@@ -449,8 +427,7 @@ export async function addCommand(packages: string[], options: AddOptions): Promi
|
|
|
449
427
|
const lockedPkg: LockedPackage = {
|
|
450
428
|
name: result.name,
|
|
451
429
|
version: result.version,
|
|
452
|
-
resolved: result.source === 'local' ? result.path : result.path
|
|
453
|
-
integrity
|
|
430
|
+
resolved: result.source === 'local' ? result.path : result.path
|
|
454
431
|
};
|
|
455
432
|
lockPackage(projectDir, lockedPkg);
|
|
456
433
|
}
|
package/src/commands/run.ts
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { outputError } from '../lib/cli.js';
|
|
6
|
-
import { executeTool, ExecuteToolError } from '../core/execute.js';
|
|
6
|
+
import { executeTool, ExecuteToolError, type ScopeLevel } from '../core/execute.js';
|
|
7
7
|
|
|
8
8
|
interface RunOptions {
|
|
9
9
|
env?: string[];
|
|
10
|
+
scope?: string;
|
|
11
|
+
sandbox?: boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export async function runCommand(
|
|
@@ -26,6 +28,19 @@ export async function runCommand(
|
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
// Validate scope option
|
|
32
|
+
let scope: ScopeLevel = 'full';
|
|
33
|
+
if (options.scope) {
|
|
34
|
+
const validScopes: ScopeLevel[] = ['read', 'write', 'full'];
|
|
35
|
+
if (!validScopes.includes(options.scope as ScopeLevel)) {
|
|
36
|
+
outputError('INVALID_INPUT', `Invalid scope: ${options.scope}`, {
|
|
37
|
+
validScopes,
|
|
38
|
+
hint: 'Use --scope read, --scope write, or --scope full'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
scope = options.scope as ScopeLevel;
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
try {
|
|
30
45
|
const result = await executeTool({
|
|
31
46
|
packageName,
|
|
@@ -33,7 +48,9 @@ export async function runCommand(
|
|
|
33
48
|
args,
|
|
34
49
|
cwd: process.cwd(),
|
|
35
50
|
env: extraEnv,
|
|
36
|
-
capture: 'inherit'
|
|
51
|
+
capture: 'inherit',
|
|
52
|
+
scope,
|
|
53
|
+
sandbox: options.sandbox ?? false
|
|
37
54
|
});
|
|
38
55
|
process.exitCode = result.exitCode;
|
|
39
56
|
return;
|
package/src/core/config.ts
CHANGED
|
@@ -104,6 +104,12 @@ export interface Config {
|
|
|
104
104
|
port: number;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
// Audit logging configuration
|
|
108
|
+
audit: {
|
|
109
|
+
/** Enable audit logging for MCP tool calls */
|
|
110
|
+
enabled: boolean;
|
|
111
|
+
};
|
|
112
|
+
|
|
107
113
|
// Telemetry (future)
|
|
108
114
|
telemetry: boolean;
|
|
109
115
|
}
|
|
@@ -128,6 +134,9 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
128
134
|
transport: 'stdio',
|
|
129
135
|
port: 3100
|
|
130
136
|
},
|
|
137
|
+
audit: {
|
|
138
|
+
enabled: true
|
|
139
|
+
},
|
|
131
140
|
telemetry: false
|
|
132
141
|
};
|
|
133
142
|
|
|
@@ -197,6 +206,12 @@ function deepMerge(target: Config, source: Partial<Config>): Config {
|
|
|
197
206
|
port: source.mcp.port ?? target.mcp.port
|
|
198
207
|
};
|
|
199
208
|
}
|
|
209
|
+
// Deep merge audit config
|
|
210
|
+
if (source.audit !== undefined) {
|
|
211
|
+
result.audit = {
|
|
212
|
+
enabled: source.audit.enabled ?? target.audit.enabled
|
|
213
|
+
};
|
|
214
|
+
}
|
|
200
215
|
|
|
201
216
|
return result;
|
|
202
217
|
}
|
package/src/core/execute.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { log } from '../lib/cli.js';
|
|
|
15
15
|
import { findPackage } from './config.js';
|
|
16
16
|
import { loadManifest, type Manifest } from './manifest.js';
|
|
17
17
|
import { getSecret } from './secrets.js';
|
|
18
|
-
import { checkPackageIntegrity } from './lockfile.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Expand ~ to home directory in paths
|
|
@@ -32,6 +31,14 @@ function expandTilde(path: string): string {
|
|
|
32
31
|
|
|
33
32
|
export type ExecuteCaptureMode = 'inherit' | 'pipe';
|
|
34
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Permission scope levels for tool execution
|
|
36
|
+
* - read: Only allow read operations (no mutations)
|
|
37
|
+
* - write: Allow write operations but no destructive actions
|
|
38
|
+
* - full: Full access (default)
|
|
39
|
+
*/
|
|
40
|
+
export type ScopeLevel = 'read' | 'write' | 'full';
|
|
41
|
+
|
|
35
42
|
export interface ExecuteToolOptions {
|
|
36
43
|
packageName: string;
|
|
37
44
|
command?: string;
|
|
@@ -42,6 +49,10 @@ export interface ExecuteToolOptions {
|
|
|
42
49
|
capture: ExecuteCaptureMode;
|
|
43
50
|
timeoutMs?: number;
|
|
44
51
|
teeStderr?: boolean;
|
|
52
|
+
/** Permission scope for the tool */
|
|
53
|
+
scope?: ScopeLevel;
|
|
54
|
+
/** Run in sandboxed environment with restricted file system access */
|
|
55
|
+
sandbox?: boolean;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
export interface ExecuteToolResult {
|
|
@@ -349,6 +360,39 @@ function buildRuntimeCommand(entryPath: string, cmdArgs: string[]): { execCmd: s
|
|
|
349
360
|
return { execCmd: 'node', execArgs: [entryPath, ...cmdArgs], runtime: 'node' };
|
|
350
361
|
}
|
|
351
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Build security environment variables for scope and sandbox restrictions
|
|
365
|
+
*/
|
|
366
|
+
function buildSecurityEnv(
|
|
367
|
+
scope: ScopeLevel,
|
|
368
|
+
sandbox: boolean,
|
|
369
|
+
cwd: string
|
|
370
|
+
): Record<string, string> {
|
|
371
|
+
const env: Record<string, string> = {};
|
|
372
|
+
|
|
373
|
+
// Set scope environment variable for tools to respect
|
|
374
|
+
env.CLI4AI_SCOPE = scope;
|
|
375
|
+
|
|
376
|
+
// Sandbox restrictions
|
|
377
|
+
if (sandbox) {
|
|
378
|
+
env.CLI4AI_SANDBOX = '1';
|
|
379
|
+
|
|
380
|
+
// Restrict file system access to temp directories and package directory
|
|
381
|
+
// Tools should check these env vars and restrict their operations
|
|
382
|
+
const tmpDir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || '/tmp';
|
|
383
|
+
env.CLI4AI_SANDBOX_ALLOWED_PATHS = [
|
|
384
|
+
tmpDir,
|
|
385
|
+
cwd, // Allow access to current working directory
|
|
386
|
+
].join(':');
|
|
387
|
+
|
|
388
|
+
// Restrict network access in sandbox mode
|
|
389
|
+
// Tools should check this and limit network operations
|
|
390
|
+
env.CLI4AI_SANDBOX_NETWORK = 'restricted';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return env;
|
|
394
|
+
}
|
|
395
|
+
|
|
352
396
|
async function ensureRuntimeAvailable(): Promise<void> {
|
|
353
397
|
if (!commandExists('node')) {
|
|
354
398
|
log('⚠️ Node.js is required to run this tool\n');
|
|
@@ -389,27 +433,6 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
|
|
|
389
433
|
|
|
390
434
|
const manifest = loadManifest(pkg.path);
|
|
391
435
|
|
|
392
|
-
const integrityCheck = checkPackageIntegrity(invocationDir, options.packageName, pkg.path);
|
|
393
|
-
if (!integrityCheck.valid) {
|
|
394
|
-
log(`\n⚠️ SECURITY WARNING: Package integrity check failed for ${options.packageName}`);
|
|
395
|
-
if (integrityCheck.error) log(` ${integrityCheck.error}`);
|
|
396
|
-
if (integrityCheck.expected && integrityCheck.actual) {
|
|
397
|
-
log(` Expected: ${integrityCheck.expected.slice(0, 30)}...`);
|
|
398
|
-
log(` Actual: ${integrityCheck.actual.slice(0, 30)}...`);
|
|
399
|
-
}
|
|
400
|
-
log(`\n The package may have been tampered with or modified since installation.`);
|
|
401
|
-
log(` To fix: reinstall with "cli4ai remove ${options.packageName} && cli4ai add ${options.packageName}"\n`);
|
|
402
|
-
|
|
403
|
-
const shouldContinue = await confirm('Continue anyway? (NOT RECOMMENDED)');
|
|
404
|
-
if (!shouldContinue) {
|
|
405
|
-
throw new ExecuteToolError('INTEGRITY_ERROR', 'Package integrity verification failed', {
|
|
406
|
-
package: options.packageName,
|
|
407
|
-
hint: `Reinstall the package with: cli4ai remove ${options.packageName} && cli4ai add ${options.packageName}`
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
log('');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
436
|
await ensureRuntimeAvailable();
|
|
414
437
|
|
|
415
438
|
await checkPeerDependencies(pkg.path);
|
|
@@ -431,6 +454,19 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
|
|
|
431
454
|
|
|
432
455
|
const teeStderr = options.teeStderr ?? true;
|
|
433
456
|
|
|
457
|
+
// Build security environment for scope and sandbox
|
|
458
|
+
const scope = options.scope ?? 'full';
|
|
459
|
+
const sandbox = options.sandbox ?? false;
|
|
460
|
+
const securityEnv = buildSecurityEnv(scope, sandbox, invocationDir);
|
|
461
|
+
|
|
462
|
+
// Log security restrictions if active
|
|
463
|
+
if (scope !== 'full' || sandbox) {
|
|
464
|
+
const restrictions: string[] = [];
|
|
465
|
+
if (scope !== 'full') restrictions.push(`scope=${scope}`);
|
|
466
|
+
if (sandbox) restrictions.push('sandbox=enabled');
|
|
467
|
+
log(`🔒 Security: ${restrictions.join(', ')}`);
|
|
468
|
+
}
|
|
469
|
+
|
|
434
470
|
if (options.capture === 'inherit') {
|
|
435
471
|
const proc = spawn(execCmd, execArgs, {
|
|
436
472
|
stdio: 'inherit',
|
|
@@ -443,6 +479,7 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
|
|
|
443
479
|
C4AI_PACKAGE_NAME: pkg.name,
|
|
444
480
|
C4AI_ENTRY: entryPath,
|
|
445
481
|
...secretsEnv,
|
|
482
|
+
...securityEnv,
|
|
446
483
|
...(options.env ?? {})
|
|
447
484
|
}
|
|
448
485
|
});
|
|
@@ -475,6 +512,7 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
|
|
|
475
512
|
C4AI_PACKAGE_NAME: pkg.name,
|
|
476
513
|
C4AI_ENTRY: entryPath,
|
|
477
514
|
...secretsEnv,
|
|
515
|
+
...securityEnv,
|
|
478
516
|
...(options.env ?? {})
|
|
479
517
|
}
|
|
480
518
|
});
|
package/src/core/lockfile.ts
CHANGED
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tracks installed packages with exact versions and sources
|
|
5
5
|
* for reproducible installations.
|
|
6
|
-
*
|
|
7
|
-
* SECURITY: Includes SRI (Subresource Integrity) hash verification
|
|
8
|
-
* to detect tampered packages.
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
import { readFileSync, writeFileSync, renameSync, existsSync
|
|
12
|
-
import { resolve
|
|
13
|
-
import { createHash } from 'crypto';
|
|
8
|
+
import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
14
10
|
|
|
15
11
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
12
|
// TYPES
|
|
@@ -36,125 +32,6 @@ export interface Lockfile {
|
|
|
36
32
|
export const LOCKFILE_NAME = 'cli4ai.lock';
|
|
37
33
|
export const LOCKFILE_VERSION = 1;
|
|
38
34
|
|
|
39
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
40
|
-
// INTEGRITY HASHING (SRI - Subresource Integrity)
|
|
41
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Compute SHA-512 hash of a file and return SRI format string
|
|
45
|
-
*/
|
|
46
|
-
export function computeFileIntegrity(filePath: string): string {
|
|
47
|
-
const content = readFileSync(filePath);
|
|
48
|
-
const hash = createHash('sha512').update(content).digest('base64');
|
|
49
|
-
return `sha512-${hash}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Compute SHA-512 hash of a directory's contents (deterministic).
|
|
54
|
-
* Hashes all files in sorted order to produce a reproducible hash.
|
|
55
|
-
*/
|
|
56
|
-
export function computeDirectoryIntegrity(dirPath: string): string {
|
|
57
|
-
const hash = createHash('sha512');
|
|
58
|
-
const files: string[] = [];
|
|
59
|
-
|
|
60
|
-
// Recursively collect all files
|
|
61
|
-
function collectFiles(dir: string): void {
|
|
62
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
63
|
-
for (const entry of entries) {
|
|
64
|
-
// Skip node_modules and hidden directories
|
|
65
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
66
|
-
|
|
67
|
-
const fullPath = join(dir, entry.name);
|
|
68
|
-
if (entry.isDirectory()) {
|
|
69
|
-
collectFiles(fullPath);
|
|
70
|
-
} else if (entry.isFile()) {
|
|
71
|
-
files.push(fullPath);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
collectFiles(dirPath);
|
|
77
|
-
|
|
78
|
-
// Sort files for deterministic ordering
|
|
79
|
-
files.sort();
|
|
80
|
-
|
|
81
|
-
// Hash each file's relative path and content
|
|
82
|
-
for (const filePath of files) {
|
|
83
|
-
const relativePath = relative(dirPath, filePath);
|
|
84
|
-
const content = readFileSync(filePath);
|
|
85
|
-
|
|
86
|
-
// Include path in hash for structural integrity
|
|
87
|
-
hash.update(relativePath);
|
|
88
|
-
hash.update(content);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return `sha512-${hash.digest('base64')}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Verify a package's integrity against stored hash
|
|
96
|
-
*/
|
|
97
|
-
export function verifyIntegrity(dirPath: string, expectedIntegrity: string): boolean {
|
|
98
|
-
if (!expectedIntegrity) return true; // No integrity check if not stored
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const actualIntegrity = computeDirectoryIntegrity(dirPath);
|
|
102
|
-
return actualIntegrity === expectedIntegrity;
|
|
103
|
-
} catch {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Result of integrity verification
|
|
110
|
-
*/
|
|
111
|
-
export interface IntegrityCheckResult {
|
|
112
|
-
valid: boolean;
|
|
113
|
-
expected?: string;
|
|
114
|
-
actual?: string;
|
|
115
|
-
error?: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Check integrity of a locked package
|
|
120
|
-
*/
|
|
121
|
-
export function checkPackageIntegrity(
|
|
122
|
-
projectDir: string,
|
|
123
|
-
packageName: string,
|
|
124
|
-
packagePath: string
|
|
125
|
-
): IntegrityCheckResult {
|
|
126
|
-
const locked = getLockedPackage(projectDir, packageName);
|
|
127
|
-
|
|
128
|
-
if (!locked) {
|
|
129
|
-
return { valid: true }; // No lock entry, can't verify
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!locked.integrity) {
|
|
133
|
-
return { valid: true }; // No integrity hash stored
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const actualIntegrity = computeDirectoryIntegrity(packagePath);
|
|
138
|
-
|
|
139
|
-
if (actualIntegrity === locked.integrity) {
|
|
140
|
-
return { valid: true, expected: locked.integrity, actual: actualIntegrity };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
valid: false,
|
|
145
|
-
expected: locked.integrity,
|
|
146
|
-
actual: actualIntegrity,
|
|
147
|
-
error: 'Integrity mismatch - package may have been tampered with'
|
|
148
|
-
};
|
|
149
|
-
} catch (err) {
|
|
150
|
-
return {
|
|
151
|
-
valid: false,
|
|
152
|
-
expected: locked.integrity,
|
|
153
|
-
error: `Failed to compute integrity: ${err instanceof Error ? err.message : String(err)}`
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
35
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
159
36
|
// FUNCTIONS
|
|
160
37
|
// ═══════════════════════════════════════════════════════════════════════════
|
package/src/mcp/server.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { homedir } from 'os';
|
|
|
17
17
|
import { type Manifest } from '../core/manifest.js';
|
|
18
18
|
import { manifestToMcpTools, type McpTool } from './adapter.js';
|
|
19
19
|
import { getSecret } from '../core/secrets.js';
|
|
20
|
+
import { loadConfig } from '../core/config.js';
|
|
20
21
|
|
|
21
22
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
23
|
// SECURITY: Audit logging and rate limiting
|
|
@@ -33,6 +34,7 @@ interface RateLimitEntry {
|
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Audit log an MCP tool call for security tracking
|
|
37
|
+
* Can be disabled via `cli4ai config set audit.enabled false`
|
|
36
38
|
*/
|
|
37
39
|
function auditLog(
|
|
38
40
|
packageName: string,
|
|
@@ -42,6 +44,12 @@ function auditLog(
|
|
|
42
44
|
errorMessage?: string
|
|
43
45
|
): void {
|
|
44
46
|
try {
|
|
47
|
+
// Check if audit logging is enabled
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
if (!config.audit?.enabled) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
if (!existsSync(AUDIT_LOG_DIR)) {
|
|
46
54
|
mkdirSync(AUDIT_LOG_DIR, { recursive: true });
|
|
47
55
|
}
|