feat: 升级至 v0.3.16 — 工单修复 51 项 + 审查修复 30 项(安全加固/适配器/工具系统/数据层/前端)
This commit is contained in:
@@ -35,6 +35,84 @@ function safeParseArgs(raw: string): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
// #6 修复: MCP stdio 命令安全校验
|
||||
/**
|
||||
* 允许的 MCP Server 启动命令白名单。
|
||||
* 仅允许常见的 MCP Server 运行时,防止任意命令执行。
|
||||
*/
|
||||
const ALLOWED_MCP_COMMANDS = new Set([
|
||||
'npx', 'node', 'npm',
|
||||
'python', 'python3', 'uv', 'uvx',
|
||||
'bun', 'deno',
|
||||
]);
|
||||
|
||||
/**
|
||||
* #6 修复: 校验 MCP Server 的 command 和 args,防止命令注入
|
||||
*
|
||||
* 1. 命令白名单:只允许已知的运行时命令
|
||||
* 2. 参数注入检测:拒绝包含 shell 元字符的参数
|
||||
*
|
||||
* @param command MCP Server 启动命令
|
||||
* @param args MCP Server 启动参数
|
||||
* @throws 如果命令不在白名单或参数包含 shell 元字符
|
||||
*/
|
||||
function validateMcpCommand(command: string, args: string[]): void {
|
||||
// 提取命令 basename(处理 /usr/bin/node、C:\node\node.exe 等路径)
|
||||
const baseCmd = command.split(/[\\/]/).pop()?.replace(/\.exe$/i, '') ?? command;
|
||||
|
||||
if (!ALLOWED_MCP_COMMANDS.has(baseCmd)) {
|
||||
throw new Error(
|
||||
`MCP command "${baseCmd}" is not in the allowed list: ${[...ALLOWED_MCP_COMMANDS].join(', ')}. ` +
|
||||
`For security reasons, only standard MCP runtimes are permitted.`,
|
||||
);
|
||||
}
|
||||
|
||||
// 审查修复: 移除 {}() 字符 — StdioClientTransport 用 spawn(不经 shell),
|
||||
// 这些字符无注入风险,但 MCP Server 的 args 常含 JSON 配置(如 --config {"port":3000})会被误拒。
|
||||
// 保留 ; & | ` $ < > 换行 等高危字符。
|
||||
const shellMetacharPattern = /[;&|`$<>\n\r]/;
|
||||
for (const arg of args) {
|
||||
if (shellMetacharPattern.test(arg)) {
|
||||
throw new Error(
|
||||
`MCP command argument contains shell metacharacters and was rejected: ${arg.slice(0, 100)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #6 修复 + 审查修复: 构建安全的子进程环境变量
|
||||
*
|
||||
* 审查修复: 原白名单方案过于激进,剥离了 MCP Server 运行所需的 npm_config_*、代理变量等,
|
||||
* 导致 MCP Server 无法启动。改为黑名单方案:剔除包含敏感后缀的变量,保留其余。
|
||||
*
|
||||
* 注意: GITHUB_TOKEN / SLACK_BOT_TOKEN 等含 _TOKEN 后缀的变量也会被过滤。
|
||||
* 如果 MCP Server 需要这些凭证,应通过 MCP Server 配置文件传递,而非环境变量。
|
||||
*/
|
||||
function buildSafeEnv(): Record<string, string> {
|
||||
// 敏感变量后缀黑名单 — 匹配这些后缀的变量不会被传递给子进程
|
||||
const SENSITIVE_SUFFIXES = [
|
||||
'_API_KEY', '_TOKEN', '_SECRET', '_PASSWORD', '_PASSWD',
|
||||
'_CREDENTIAL', '_CREDENTIALS', '_PRIVATE_KEY',
|
||||
];
|
||||
// 敏感变量名黑名单(精确匹配)
|
||||
const SENSITIVE_KEYS = new Set([
|
||||
'DEEPSEEK_API_KEY', 'AGNES_API_KEY', 'MIMO_API_KEY',
|
||||
'GITEA_PASSWORD', 'DATABASE_PASSWORD',
|
||||
]);
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
for (const [key, val] of Object.entries(process.env)) {
|
||||
if (!val) continue;
|
||||
// 跳过敏感变量名
|
||||
if (SENSITIVE_KEYS.has(key)) continue;
|
||||
// 跳过敏感后缀变量
|
||||
if (SENSITIVE_SUFFIXES.some((suffix) => key.toUpperCase().endsWith(suffix))) continue;
|
||||
env[key] = val;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
// ===== 类型定义 =====
|
||||
|
||||
export type MCPServerStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
|
||||
@@ -178,9 +256,13 @@ export class MCPManager {
|
||||
if (config.transport === 'stdio' && config.command) {
|
||||
// stdio 模式
|
||||
const args = config.args ?? [];
|
||||
// #6 修复: 命令白名单 + 参数元字符检测,防止命令注入
|
||||
validateMcpCommand(config.command, args);
|
||||
transport = new StdioClientTransport({
|
||||
command: config.command,
|
||||
args,
|
||||
// #6 修复: 不透传完整 process.env,仅保留 MCP Server 运行所需的最小环境变量
|
||||
env: buildSafeEnv(),
|
||||
});
|
||||
} else if (config.transport === 'sse' && config.url) {
|
||||
// SSE 模式(远程 HTTP)
|
||||
|
||||
Reference in New Issue
Block a user