From 7d9ef12f69ce68845952f7a7d41e945e15fff90f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 05:05:54 +0000 Subject: [PATCH] fix: address code review feedback - rate limiting, PAT validation, tier registration - Add per-route rate limiting to industry routes - Validate PAT format (ghp_ / github_pat_ prefix) - Fix industry ID generation to use counter - Reject unknown devs in quota check instead of defaulting to P2 - Extract admin IDs to constant in github-server.js Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/914ad98a-eb79-43bf-9d77-414bb7771bcb --- backend/api-server/routes/industry.js | 49 +++++++++++++++++++++------ mcp-servers/github-server.js | 7 ++-- scripts/quota-middleware.js | 15 ++++++-- 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/backend/api-server/routes/industry.js b/backend/api-server/routes/industry.js index 71a3b2a0..19456fe8 100644 --- a/backend/api-server/routes/industry.js +++ b/backend/api-server/routes/industry.js @@ -19,6 +19,32 @@ var authModule = require('./auth'); var requireSession = authModule.requireSession; var DEV_DATABASE = authModule.DEV_DATABASE; +// Rate limiting (same pattern as auth.js) +var requestCounts = new Map(); +var RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute window +var RATE_LIMIT_MAX = 10; // max 10 requests per minute + +function rateLimit(req, res, next) { + var ip = req.ip || req.connection.remoteAddress || 'unknown'; + var now = Date.now(); + var entry = requestCounts.get(ip); + if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) { + requestCounts.set(ip, { windowStart: now, count: 1 }); + return next(); + } + entry.count++; + if (entry.count > RATE_LIMIT_MAX) { + return res.status(429).json({ + error: true, + code: 'RATE_LIMITED', + message: '请求过于频繁,请稍后再试' + }); + } + next(); +} + +// Apply rate limiting to all industry routes via per-route middleware + /** * Check if a dev ID is a registered developer * @param {string} devId @@ -29,13 +55,14 @@ function isRegisteredDev(devId) { } /** - * Generate a new industry ID + * Generate a new industry ID using a counter * @returns {string} */ +var industryCounter = 1; function generateIndustryId() { - // Simple incremental ID; in production this would query existing IDs - var timestamp = Date.now().toString(36).toUpperCase(); - return 'IND-' + timestamp.slice(-3).padStart(3, '0'); + var id = 'IND-' + String(industryCounter).padStart(3, '0'); + industryCounter++; + return id; } /** @@ -52,7 +79,7 @@ function hashPAT(pat) { * Register a new industry with a representative developer * Requires authenticated session (DEV-XXX) */ -router.post('/industry/register', requireSession, function(req, res) { +router.post('/industry/register', rateLimit, requireSession, function(req, res) { var industry_name = (req.body.industry_name || '').trim(); var rep_dev_id = (req.body.rep_dev_id || '').trim().toUpperCase(); var github_username = (req.body.github_username || '').trim(); @@ -88,13 +115,13 @@ router.post('/industry/register', requireSession, function(req, res) { }); } - // Step 3: validate PAT format (basic check — real validation against GitHub API - // is done asynchronously after registration) - if (pat.length < 10) { + // Step 3: validate PAT format (GitHub classic tokens start with ghp_, + // fine-grained tokens start with github_pat_) + if (pat.length < 10 || !(pat.startsWith('ghp_') || pat.startsWith('github_pat_'))) { return res.status(400).json({ error: true, code: 'INVALID_PAT', - message: 'PAT 格式无效' + message: 'PAT 格式无效 · 需要以 ghp_ 或 github_pat_ 开头的有效 GitHub Personal Access Token' }); } @@ -148,7 +175,7 @@ router.post('/industry/register', requireSession, function(req, res) { * GET /api/industry/list * List all registered industries (admin only) */ -router.get('/industry/list', requireSession, function(req, res) { +router.get('/industry/list', rateLimit, requireSession, function(req, res) { var sessionDev = req.sessionDev; if (!sessionDev || sessionDev.level < 3) { return res.status(403).json({ @@ -177,7 +204,7 @@ router.get('/industry/list', requireSession, function(req, res) { * GET /api/industry/status/:id * Get status of a specific industry */ -router.get('/industry/status/:id', requireSession, function(req, res) { +router.get('/industry/status/:id', rateLimit, requireSession, function(req, res) { var industryId = req.params.id; // In production: lookup from database diff --git a/mcp-servers/github-server.js b/mcp-servers/github-server.js index ec6bd7c2..f94ff989 100644 --- a/mcp-servers/github-server.js +++ b/mcp-servers/github-server.js @@ -96,6 +96,9 @@ var tools = [ } ]; +// System admin IDs with full access to all repos +var ADMIN_IDS = ['TCS-0002', 'DEV-000']; + /** * Permission matrix for GitHub tools * @param {string} devId - Developer ID @@ -103,8 +106,8 @@ var tools = [ * @returns {{ allowed: boolean, reason?: string }} */ function checkPermission(devId, repo) { - // TCS-0002 (冰朔) has access to all repos - if (devId === 'TCS-0002' || devId === 'DEV-000') { + // Admin users have access to all repos + if (ADMIN_IDS.indexOf(devId) >= 0) { return { allowed: true }; } diff --git a/scripts/quota-middleware.js b/scripts/quota-middleware.js index 6f6caddf..74f79fa8 100644 --- a/scripts/quota-middleware.js +++ b/scripts/quota-middleware.js @@ -26,7 +26,9 @@ function loadDailyQuota() { try { const data = JSON.parse(fs.readFileSync(QUOTA_LOG_PATH, 'utf8')); if (data.date === today) return data; - } catch (e) { /* file missing or date mismatch — reset */ } + } catch (e) { + // File missing or corrupted — will reset to fresh daily quota + } return { date: today, @@ -49,13 +51,22 @@ function getTier(devId) { for (const [tier, tierConfig] of Object.entries(config.priority_tiers)) { if (tierConfig.members && tierConfig.members.includes(devId)) return tier; } - return 'P2'; // default lowest priority + return null; // unknown developer — not in any tier } function checkQuota(devId) { const config = getQuotaConfig(); const daily = loadDailyQuota(); const tier = getTier(devId); + + if (!tier) { + return { + allowed: false, + reason: '开发者 ' + devId + ' 未注册到任何配额层级', + suggestion: '请联系管理员将你添加到配额层级中' + }; + } + const tierConfig = config.priority_tiers[tier]; const totalDaily = config.quota_pool.daily_total;