cloudflare-mcp-smart-proxy 1.4.1 → 1.4.2

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/connector-cli.js CHANGED
@@ -29,6 +29,7 @@ function printUsage() {
29
29
  console.error(' --app-server-socket <path> Current runtime Codex app-server socket');
30
30
  console.error(' --runtime-id <id> Current Codex runtime identity');
31
31
  console.error(' --dry-run Show output without writing');
32
+ console.error(' --show-secrets Explicitly include credentials in print output');
32
33
  console.error('');
33
34
  console.error('The reload codex command uses the existing Codex config and requires no credentials.');
34
35
  }
@@ -47,8 +48,8 @@ function parseArgs(argv) {
47
48
  }
48
49
 
49
50
  const key = token.slice(2);
50
- if (key === 'dry-run') {
51
- options.dryRun = true;
51
+ if (key === 'dry-run' || key === 'show-secrets') {
52
+ options[key === 'dry-run' ? 'dryRun' : key] = true;
52
53
  continue;
53
54
  }
54
55
 
@@ -95,7 +96,7 @@ function buildInstallOptions(options) {
95
96
  };
96
97
  }
97
98
 
98
- function printInstallResult(result, { includeContent = false } = {}) {
99
+ function printInstallResult(result, { includeContent = false, includeSecrets = false } = {}) {
99
100
  console.error(`ecosystem: ${result.ecosystem}`);
100
101
  console.error(`scope: ${result.scope}`);
101
102
  console.error(`target: ${result.targetFile}`);
@@ -111,7 +112,11 @@ function printInstallResult(result, { includeContent = false } = {}) {
111
112
  }
112
113
  }
113
114
  if (includeContent) {
114
- process.stdout.write(`${result.renderedContent}\n`);
115
+ const apiKey = result.serverDefinition?.env?.CLOUDFLARE_MCP_API_KEY || '';
116
+ const renderedContent = includeSecrets
117
+ ? result.renderedContent
118
+ : (apiKey ? result.renderedContent.split(apiKey).join('[REDACTED]') : result.renderedContent);
119
+ process.stdout.write(`${renderedContent}\n`);
115
120
  }
116
121
  }
117
122
 
@@ -177,7 +182,7 @@ async function main() {
177
182
 
178
183
  if (options.command === 'print') {
179
184
  const result = printReferenceConnectorConfig(buildInstallOptions(options));
180
- printInstallResult(result, { includeContent: true });
185
+ printInstallResult(result, { includeContent: true, includeSecrets: options['show-secrets'] === true });
181
186
  return;
182
187
  }
183
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-mcp-smart-proxy",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,9 @@
36
36
  "@hono/node-server": "2.0.12",
37
37
  "ajv": "8.20.0",
38
38
  "body-parser": "2.3.0",
39
- "fast-uri": "3.1.4",
39
+ "fast-uri": "3.1.5",
40
+ "hono": "4.12.34",
41
+ "ip-address": "10.4.0",
40
42
  "path-to-regexp": "8.4.0",
41
43
  "qs": "6.15.3"
42
44
  }
@@ -214,13 +214,13 @@ export class LocalToolExecutor {
214
214
  },
215
215
  {
216
216
  name: 'execute_command',
217
- description: 'Execute a shell command locally',
217
+ description: 'Execute a local process without shell interpretation',
218
218
  inputSchema: {
219
219
  type: 'object',
220
220
  properties: {
221
221
  command: {
222
222
  type: 'string',
223
- description: 'Shell command to execute'
223
+ description: 'Executable and arguments to run'
224
224
  },
225
225
  cwd: {
226
226
  type: 'string',
@@ -4,6 +4,17 @@
4
4
 
5
5
  import { spawn } from 'child_process';
6
6
  import path from 'path';
7
+ import { FileOperations } from './file-operations.js';
8
+
9
+ function parseCommand(command) {
10
+ const tokens = [];
11
+ const pattern = /"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s]+)/g;
12
+ let match;
13
+ while ((match = pattern.exec(command)) !== null) {
14
+ tokens.push((match[1] ?? match[2] ?? match[3]).replace(/\\([\\"])/g, '$1'));
15
+ }
16
+ return tokens;
17
+ }
7
18
 
8
19
  export class CommandExecutor {
9
20
  /**
@@ -16,17 +27,21 @@ export class CommandExecutor {
16
27
  throw new Error('command parameter is required');
17
28
  }
18
29
 
19
- const workingDir = cwd ? path.resolve(workspaceRoot, cwd) : workspaceRoot;
30
+ const workingDir = cwd ? FileOperations.validatePath(cwd, workspaceRoot) : FileOperations.validatePath('.', workspaceRoot);
20
31
 
21
32
  return new Promise((resolve, reject) => {
22
33
  // 解析命令和参数
23
- const parts = command.trim().split(/\s+/);
34
+ const parts = parseCommand(command.trim());
24
35
  const cmd = parts[0];
25
36
  const args = parts.slice(1);
37
+ if (!cmd) {
38
+ reject(new Error('command parameter must include an executable'));
39
+ return;
40
+ }
26
41
 
27
42
  // 安全限制:禁止某些危险命令
28
- const dangerousCommands = ['rm', 'del', 'format', 'shutdown', 'reboot'];
29
- if (dangerousCommands.some(danger => cmd.includes(danger))) {
43
+ const dangerousCommands = new Set(['rm', 'del', 'format', 'shutdown', 'reboot']);
44
+ if (dangerousCommands.has(path.basename(cmd).toLowerCase())) {
30
45
  reject(new Error(`Command "${cmd}" is not allowed for security reasons`));
31
46
  return;
32
47
  }
@@ -38,9 +53,13 @@ export class CommandExecutor {
38
53
  // 执行命令
39
54
  const proc = spawn(cmd, args, {
40
55
  cwd: workingDir,
41
- shell: true,
56
+ shell: false,
42
57
  stdio: ['ignore', 'pipe', 'pipe']
43
58
  });
59
+ const timeout = setTimeout(() => {
60
+ proc.kill();
61
+ reject(new Error('Command execution timeout (30s)'));
62
+ }, 30000);
44
63
 
45
64
  // 收集输出
46
65
  proc.stdout.on('data', (data) => {
@@ -53,6 +72,7 @@ export class CommandExecutor {
53
72
 
54
73
  // 处理完成
55
74
  proc.on('close', (code) => {
75
+ clearTimeout(timeout);
56
76
  const duration = Date.now() - startTime;
57
77
 
58
78
  resolve({
@@ -68,15 +88,9 @@ export class CommandExecutor {
68
88
 
69
89
  // 处理错误
70
90
  proc.on('error', (error) => {
91
+ clearTimeout(timeout);
71
92
  reject(new Error(`Command execution failed: ${error.message}`));
72
93
  });
73
-
74
- // 超时保护(30秒)
75
- setTimeout(() => {
76
- proc.kill();
77
- reject(new Error('Command execution timeout (30s)'));
78
- }, 30000);
79
94
  });
80
95
  }
81
96
  }
82
-
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import fs from 'fs/promises';
6
+ import fsSync from 'fs';
6
7
  import path from 'path';
7
8
 
8
9
  export class FileOperations {
@@ -16,14 +17,28 @@ export class FileOperations {
16
17
  }
17
18
 
18
19
  // 限制在工作目录内
19
- const resolvedPath = path.resolve(workspaceRoot, filePath);
20
- const resolvedRoot = path.resolve(workspaceRoot);
20
+ const resolvedRoot = fsSync.realpathSync(path.resolve(workspaceRoot));
21
+ const resolvedPath = path.resolve(resolvedRoot, filePath);
22
+ const relativePath = path.relative(resolvedRoot, resolvedPath);
21
23
 
22
- if (!resolvedPath.startsWith(resolvedRoot)) {
24
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
23
25
  throw new Error('File path outside workspace root');
24
26
  }
25
-
26
- return resolvedPath;
27
+
28
+ let existingAncestor = resolvedPath;
29
+ while (!fsSync.existsSync(existingAncestor)) {
30
+ const parent = path.dirname(existingAncestor);
31
+ if (parent === existingAncestor) break;
32
+ existingAncestor = parent;
33
+ }
34
+ const realAncestor = fsSync.realpathSync(existingAncestor);
35
+ const realTarget = path.resolve(realAncestor, path.relative(existingAncestor, resolvedPath));
36
+ const realRelativePath = path.relative(resolvedRoot, realTarget);
37
+ if (realRelativePath.startsWith('..') || path.isAbsolute(realRelativePath)) {
38
+ throw new Error('File path resolves outside workspace root');
39
+ }
40
+
41
+ return realTarget;
27
42
  }
28
43
 
29
44
  /**
@@ -208,4 +223,3 @@ export class FileOperations {
208
223
  }
209
224
  }
210
225
  }
211
-