From 37a21bedf38bc5974b78492d1b20c8ace7336fb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:24:09 +0000 Subject: [PATCH] Fix: GitHub path encoding, MCP gateway write-tool security, content preview constant Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/2127a3a7-ea58-48a4-856c-84961107632b Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/age-os/mcp-server/github-client.js | 13 ++++++++++--- server/age-os/mcp-server/tools/github-ops.js | 4 +++- server/app/server.js | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/server/age-os/mcp-server/github-client.js b/server/age-os/mcp-server/github-client.js index 2e610383..da2207b1 100644 --- a/server/age-os/mcp-server/github-client.js +++ b/server/age-os/mcp-server/github-client.js @@ -86,6 +86,13 @@ function githubRequest(method, path, body) { // 仓库内容操作 // ═══════════════════════════════════════════════════════════ +/** + * 安全编码文件路径(保留斜杠,编码各段) + */ +function encodeFilePath(filePath) { + return filePath.split('/').map(segment => encodeURIComponent(segment)).join('/'); +} + /** * 读取文件内容 * @param {string} filePath - 文件路径(相对仓库根目录) @@ -94,7 +101,7 @@ function githubRequest(method, path, body) { */ async function readFile(filePath, ref) { const { owner, repo } = GITHUB_CONFIG; - let apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + let apiPath = `/repos/${owner}/${repo}/contents/${encodeFilePath(filePath)}`; if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; const { data } = await githubRequest('GET', apiPath); @@ -120,7 +127,7 @@ async function readFile(filePath, ref) { */ async function listDirectory(dirPath, ref) { const { owner, repo } = GITHUB_CONFIG; - const encodedPath = dirPath ? encodeURIComponent(dirPath) : ''; + const encodedPath = dirPath ? encodeFilePath(dirPath) : ''; let apiPath = `/repos/${owner}/${repo}/contents/${encodedPath}`; if (ref) apiPath += `?ref=${encodeURIComponent(ref)}`; @@ -153,7 +160,7 @@ async function listDirectory(dirPath, ref) { */ async function writeFile(filePath, content, message, sha, branch) { const { owner, repo } = GITHUB_CONFIG; - const apiPath = `/repos/${owner}/${repo}/contents/${encodeURIComponent(filePath)}`; + const apiPath = `/repos/${owner}/${repo}/contents/${encodeFilePath(filePath)}`; const body = { message: message || `[铸渊] 更新 ${filePath}`, diff --git a/server/age-os/mcp-server/tools/github-ops.js b/server/age-os/mcp-server/tools/github-ops.js index e54e4157..40e41a4a 100644 --- a/server/age-os/mcp-server/tools/github-ops.js +++ b/server/age-os/mcp-server/tools/github-ops.js @@ -19,6 +19,8 @@ const github = require('../github-client'); +const CONTENT_PREVIEW_LENGTH = 500; + /** * githubReadFile — 读取仓库文件内容 * @@ -36,7 +38,7 @@ async function githubReadFile(input) { size: file.size, sha: file.sha, content: file.content, - content_preview: file.content.substring(0, 500) + (file.content.length > 500 ? '...' : '') + content_preview: file.content.substring(0, CONTENT_PREVIEW_LENGTH) + (file.content.length > CONTENT_PREVIEW_LENGTH ? '...' : '') }; } diff --git a/server/app/server.js b/server/app/server.js index 5b28ffb9..8630fa0e 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -413,6 +413,15 @@ app.get('/api/mcp/health', async (_req, res) => { }); // ─── MCP 统一工具调用(网关入口) ─── +// 安全: 写操作工具需要 caller 身份标识 +const MCP_WRITE_TOOLS = new Set([ + 'createNode', 'updateNode', 'deleteNode', + 'linkNodes', 'unlinkNodes', + 'cosWrite', 'cosDelete', 'cosArchive', + 'notionWritePage', 'notionUpdatePage', 'notionWriteSyslog', + 'githubWriteFile', 'githubTriggerDeploy' +]); + app.post('/api/mcp/call', async (req, res) => { const { tool, input, caller } = req.body; @@ -420,6 +429,15 @@ app.post('/api/mcp/call', async (req, res) => { return res.status(400).json({ error: true, code: 'MISSING_TOOL', message: '缺少 tool 参数' }); } + // 写操作工具需要 caller 身份标识 + if (MCP_WRITE_TOOLS.has(tool) && !caller) { + return res.status(403).json({ + error: true, + code: 'WRITE_REQUIRES_CALLER', + message: '写操作工具需要提供 caller 身份标识' + }); + } + try { const result = await mcpProxy('POST', '/call', { tool, input, caller: caller || 'web-gateway' }); res.status(result.statusCode).json(result.data);