cli4ai 1.0.3 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli4ai",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "The package manager for AI CLI tools - cli4ai.com",
5
5
  "type": "module",
6
6
  "bin": {
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
 
@@ -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;
@@ -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
  }
@@ -31,6 +31,14 @@ function expandTilde(path: string): string {
31
31
 
32
32
  export type ExecuteCaptureMode = 'inherit' | 'pipe';
33
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
+
34
42
  export interface ExecuteToolOptions {
35
43
  packageName: string;
36
44
  command?: string;
@@ -41,6 +49,10 @@ export interface ExecuteToolOptions {
41
49
  capture: ExecuteCaptureMode;
42
50
  timeoutMs?: number;
43
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;
44
56
  }
45
57
 
46
58
  export interface ExecuteToolResult {
@@ -348,6 +360,39 @@ function buildRuntimeCommand(entryPath: string, cmdArgs: string[]): { execCmd: s
348
360
  return { execCmd: 'node', execArgs: [entryPath, ...cmdArgs], runtime: 'node' };
349
361
  }
350
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
+
351
396
  async function ensureRuntimeAvailable(): Promise<void> {
352
397
  if (!commandExists('node')) {
353
398
  log('⚠️ Node.js is required to run this tool\n');
@@ -409,6 +454,19 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
409
454
 
410
455
  const teeStderr = options.teeStderr ?? true;
411
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
+
412
470
  if (options.capture === 'inherit') {
413
471
  const proc = spawn(execCmd, execArgs, {
414
472
  stdio: 'inherit',
@@ -421,6 +479,7 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
421
479
  C4AI_PACKAGE_NAME: pkg.name,
422
480
  C4AI_ENTRY: entryPath,
423
481
  ...secretsEnv,
482
+ ...securityEnv,
424
483
  ...(options.env ?? {})
425
484
  }
426
485
  });
@@ -453,6 +512,7 @@ export async function executeTool(options: ExecuteToolOptions): Promise<ExecuteT
453
512
  C4AI_PACKAGE_NAME: pkg.name,
454
513
  C4AI_ENTRY: entryPath,
455
514
  ...secretsEnv,
515
+ ...securityEnv,
456
516
  ...(options.env ?? {})
457
517
  }
458
518
  });
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
  }