Use with caution. Address findings before production.
Scanned 5/3/2026, 6:46:18 PMยทCached resultยทFast Scanยท45 rulesยทHow we decide โ
AIVSS Score
Medium
Severity Breakdown
0
critical
0
high
64
medium
0
low
MCP Server Information
Findings
This package carries a C security grade with a safety score of 62/100, driven primarily by 35 ansi escape injection vulnerabilities and 23 server configuration issues that could enable terminal manipulation or misconfiguration attacks. Additionally, it contains 2 hardcoded secrets and 3 resource exhaustion risks that warrant careful review before deployment. While no critical or high-severity findings were detected, the medium-severity issues are numerous enough to require mitigation or workarounds before using this in production environments.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 64 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
Showing 1โ30 of 64 findings
64 findings
Hardcoded secret detected in source. MCP servers often proxy between the model and a third-party API, so any committed credential grants that access to anyone who can read the repo. Move to an environment variable or secret manager and rotate the leaked value.
Evidence
| 146 | "command": "npx", |
| 147 | "args": ["-y", "@modelcontextprotocol/server-slack"], |
| 148 | "env": { |
| 149 | "SLACK_BOT_TOKEN": "xoxb-your-bot-token", |
| 150 | "SLACK_TEAM_ID": "T1234567890" |
| 151 | } |
| 152 | } |
Remediation
Rotate the leaked credential immediately, then replace the hardcoded value with os.environ / process.env lookup. For deployment, inject the value through your secret manager (AWS Secrets Manager, Doppler, Vault).
Hardcoded secret detected in source. MCP servers often proxy between the model and a third-party API, so any committed credential grants that access to anyone who can read the repo. Move to an environment variable or secret manager and rotate the leaked value.
Evidence
| 146 | "command": "npx", |
| 147 | "args": ["-y", "@modelcontextprotocol/server-slack"], |
| 148 | "env": { |
| 149 | "SLACK_BOT_TOKEN": "xoxb-your-bot-token", |
| 150 | "SLACK_TEAM_ID": "T1234567890" |
| 151 | } |
| 152 | } |
Remediation
Rotate the leaked credential immediately, then replace the hardcoded value with os.environ / process.env lookup. For deployment, inject the value through your secret manager (AWS Secrets Manager, Doppler, Vault).
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 461 | // Validate sessionId |
| 462 | if (!sessionId) { |
| 463 | console.error('Missing sessionId in query parameters'); |
| 464 | res.status(400).send('Missing sessionId parameter'); |
| 465 | return; |
| 466 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 162 | ); |
| 163 | return false; |
| 164 | } catch (error) { |
| 165 | console.error('Error checking bearer key request access:', error); |
| 166 | return false; |
| 167 | } |
| 168 | }; |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 758 | id: `init-${sessionId}-${Date.now()}`, |
| 759 | }; |
| 760 | |
| 761 | console.log(`[MCP] Sending initialize request for session ${sessionId}`); |
| 762 | // Use mock response to avoid sending actual HTTP response |
| 763 | await transport.handleRequest(req, mockRes, initializeRequest); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 606 | const sessionId = req.headers['mcp-session-id'] as string | undefined; |
| 607 | const group = req.params.group; |
| 608 | const body = req.body; |
| 609 | console.log( |
| 610 | `Handling MCP post request for sessionId: ${sessionId} and group: ${group}${username ? ` for user: ${username}` : ''} with body: ${JSON.stringify(body)}`, |
| 611 | ); |
| 612 | |
| 613 | // Get filtered settings based on user context (after setting user context) |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 706 | ); |
| 707 | } catch (error: any) { |
| 708 | const status = extractErrorStatus(error); |
| 709 | console.warn( |
| 710 | 'Azure OpenAI embeddings request failed after retries', |
| 711 | { |
| 712 | status: status ?? 'unknown', |
| 713 | endpoint: azureConfig.endpoint || 'missing', |
| 714 | deployment: azureConfig.embeddingDeployment || 'missing', |
| 715 | apiVersion: azureConfig.apiVersion || 'missing', |
| 716 | error, |
| 717 | }, |
| 718 | ); |
| 719 | throw error; |
| 720 | } |
| 721 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 152 | return data; |
| 153 | } catch (error) { |
| 154 | console.error('API request error:', error); |
| 155 | const errorResponse = { |
| 156 | success: false, |
| 157 | message: error instanceof Error ? error.message : 'An unknown error occurred', |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 780 | if (process.env.DEBUG === 'true') { |
| 781 | const debugCurl = buildEmbeddingsDebugCurl(config.baseURL, embeddingPayload); |
| 782 | |
| 783 | console.log( |
| 784 | `[Embedding] HTTP request details: ${JSON.stringify( |
| 785 | { |
| 786 | url: `${(config.baseURL || 'https://api.openai.com/v1').replace(/\/+$/, '')}/embeddings`, |
| 787 | method: 'POST', |
| 788 | headers: { |
| 789 | Authorization: `Bearer ${maskApiKey(config.apiKey || '')}`, |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 792 | } catch (error: any) { |
| 793 | // Check if this is a "Server not initialized" error for a newly rebuilt session |
| 794 | if (sessionId && error.message && error.message.includes('Server not initialized')) { |
| 795 | console.log( |
| 796 | `[SESSION AUTO-REBUILD] Server not initialized for ${sessionId}. Attempting to initialize with the current request.`, |
| 797 | ); |
| 798 | |
| 799 | // Check if the current request is an 'initialize' request |
| 800 | if (isInitializeRequest(req.body)) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 687 | } |
| 688 | } else if (isInitializeRequest(req.body)) { |
| 689 | // Case 3: No sessionId and this is an initialize request, create new session |
| 690 | console.log( |
| 691 | `[SESSION CREATE] No session ID provided for initialize request, creating new session${username ? ` for user: ${username}` : ''}`, |
| 692 | ); |
| 693 | transport = await createNewSession(group, username); |
| 694 | } else if ( |
| 695 | req.body && |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 818 | id: `init-${sessionId}-${Date.now()}`, |
| 819 | }; |
| 820 | |
| 821 | console.log( |
| 822 | `[SESSION AUTO-REBUILD] Sending initialize request for ${sessionId} before handling the actual request.`, |
| 823 | ); |
| 824 | try { |
| 825 | // Temporarily replace the body to send the initialize request |
| 826 | const originalBody = req.body; |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 46 | data: response.data, |
| 47 | }; |
| 48 | } catch (error) { |
| 49 | console.error('Error calling prompt', { promptName: request.promptName, server, error }); |
| 50 | return { |
| 51 | success: false, |
| 52 | error: error instanceof Error ? error.message : 'Unknown error occurred', |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 229 | return searchTerms.some((term) => searchText.includes(term)); |
| 230 | }); |
| 231 | } catch (error) { |
| 232 | console.error('Error searching cloud market servers', { query, error }); |
| 233 | throw error; |
| 234 | } |
| 235 | }; |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1637 | }; |
| 1638 | |
| 1639 | export const handleCallToolRequest = async (request: any, extra: any) => { |
| 1640 | console.log('Handling CallToolRequest for tool', summarizeToolRequestForLogging(request.params)); |
| 1641 | const startTime = Date.now(); |
| 1642 | const activityLogger = getActivityLoggingService(); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 48 | content: response?.content || [], |
| 49 | }; |
| 50 | } catch (error) { |
| 51 | console.error('Error calling tool', { toolName: request.toolName, server, error }); |
| 52 | return { |
| 53 | success: false, |
| 54 | error: error instanceof Error ? error.message : 'Unknown error occurred', |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 155 | setError(t('market.searchError')); |
| 156 | } |
| 157 | } catch (err) { |
| 158 | console.error('Error searching market servers', { query, err }); |
| 159 | setError(err instanceof Error ? err.message : String(err)); |
| 160 | } finally { |
| 161 | setLoading(false); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 438 | throw new Error('toolName parameter is required and must be a string'); |
| 439 | } |
| 440 | |
| 441 | console.log(`Handling describe_tool request for: ${toolName}`); |
| 442 | |
| 443 | // Determine group filtering |
| 444 | let group = getGroup(sessionId); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1619 | AND attname = 'embedding' |
| 1620 | `); |
| 1621 | } catch (error) { |
| 1622 | console.warn('Could not get vector type info, falling back to atttypmod query'); |
| 1623 | } |
| 1624 | |
| 1625 | // Fallback to original query |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 799 | // Check if the current request is an 'initialize' request |
| 800 | if (isInitializeRequest(req.body)) { |
| 801 | // If it is, we can just retry it. The transport should now be in the transports map. |
| 802 | console.log(`[SESSION AUTO-REBUILD] Retrying initialize request for ${sessionId}.`); |
| 803 | await transport.handleRequest(req, res, req.body); |
| 804 | } else { |
| 805 | // If not, we need to send an initialize request first. |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 2039 | arguments: promptArgs, |
| 2040 | }; |
| 2041 | // Log the final promptParams |
| 2042 | console.log('Calling getPrompt with params', { |
| 2043 | name: cleanPromptName || '', |
| 2044 | arguments: summarizeArgumentsForLogging(promptArgs), |
| 2045 | }); |
| 2046 | const prompt = await server.client?.getPrompt(promptParams); |
| 2047 | console.log('Received prompt', summarizePromptForLogging(prompt)); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 253 | const oauthUser = await resolveOAuthUserFromToken(token); |
| 254 | if (oauthUser) { |
| 255 | console.log('Authenticated request using OAuth bearer token (no matching static key)'); |
| 256 | return { valid: true, user: oauthUser }; |
| 257 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 225 | if (enabledKeys.length === 0) { |
| 226 | const oauthUser = await resolveOAuthUserFromToken(token); |
| 227 | if (oauthUser) { |
| 228 | console.log('Authenticated request using OAuth bearer token without configured keys'); |
| 229 | return { valid: true, user: oauthUser }; |
| 230 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 272 | thresholdNum = 0.4; |
| 273 | } |
| 274 | |
| 275 | console.log(`Using similarity threshold: ${thresholdNum} for query: "${query}"`); |
| 276 | |
| 277 | // Determine server filtering based on group |
| 278 | let group = getGroup(sessionId); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 704 | return; |
| 705 | } else { |
| 706 | // Case 5: No sessionId and not an initialize/notification request, return error |
| 707 | console.warn( |
| 708 | `[SESSION ERROR] No session ID provided for non-initialize request (method: ${req.body?.method})${username ? ` for user: ${username}` : ''}`, |
| 709 | ); |
| 710 | res.status(400).json({ |
| 711 | jsonrpc: '2.0', |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1848 | ? request.params.name.substring(prefix.length) |
| 1849 | : request.params.name; |
| 1850 | |
| 1851 | console.log('Invoking OpenAPI tool', { |
| 1852 | toolName: cleanToolName, |
| 1853 | serverName: serverInfo.name, |
| 1854 | arguments: summarizeArgumentsForLogging(request.params.arguments), |
| 1855 | }); |
| 1856 | |
| 1857 | // Extract passthrough headers from extra or request context |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 172 | setError(t('cloud.searchError')); |
| 173 | } |
| 174 | } catch (err) { |
| 175 | console.error('Error searching cloud servers', { query, err }); |
| 176 | setError(err instanceof Error ? err.message : String(err)); |
| 177 | } finally { |
| 178 | setLoading(false); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 151 | setCurrentPage(1); |
| 152 | applyPagination(data.data, 1); |
| 153 | } else { |
| 154 | console.error('Invalid market search results format', { query, data }); |
| 155 | setError(t('market.searchError')); |
| 156 | } |
| 157 | } catch (err) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 874 | const currentUser = userContextService.getCurrentUser(); |
| 875 | const username = currentUser?.username; |
| 876 | |
| 877 | console.log(`Handling MCP other request${username ? ` for user: ${username}` : ''}`); |
| 878 | |
| 879 | const sessionId = req.headers['mcp-session-id'] as string | undefined; |
| 880 | if (!sessionId) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 837 | // Restore the original body and retry the original request |
| 838 | req.body = originalBody; |
| 839 | console.log( |
| 840 | `[SESSION AUTO-REBUILD] Initialization complete for ${sessionId}. Retrying original request.`, |
| 841 | ); |
| 842 | await transport.handleRequest(req, res, req.body); |
| 843 | } catch (initError) { |
| 844 | console.error('[SESSION AUTO-REBUILD] Failed to initialize session on-the-fly', { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 718 | return; |
| 719 | } |
| 720 | |
| 721 | console.log(`Handling request using transport with type ${transport.constructor.name}`); |
| 722 | |
| 723 | const requestContextService = RequestContextService.getInstance(); |
| 724 | await requestContextService.runWithRequestContext(req, async () => { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 168 | setCurrentPage(1); |
| 169 | applyPagination(data.data, 1); |
| 170 | } else { |
| 171 | console.error('Invalid cloud search results format', { query, data }); |
| 172 | setError(t('cloud.searchError')); |
| 173 | } |
| 174 | } catch (err) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 230 | // Search servers by query (client-side filtering on loaded data) |
| 231 | const searchServers = useCallback( |
| 232 | async (query: string) => { |
| 233 | console.log('Searching registry servers', { query }); |
| 234 | setSearchQuery(query); |
| 235 | setCurrentPage(1); |
| 236 | setCursorHistory([]); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 832 | return response.data[0].embedding; |
| 833 | } catch (error: any) { |
| 834 | const status = extractErrorStatus(error); |
| 835 | console.warn('OpenAI-compatible embeddings request failed after retries', { |
| 836 | status: status ?? 'unknown', |
| 837 | baseURL: config.baseURL || 'default', |
| 838 | model: config.embeddingModel || 'default', |
| 839 | requestDurationMs: Date.now() - _requestStart, |
| 840 | error, |
| 841 | }); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1388 | }) |
| 1389 | .sort((a, b) => b.similarity - a.similarity); |
| 1390 | } catch (error) { |
| 1391 | console.error( |
| 1392 | 'Error searching tools by vector', |
| 1393 | safeStringify({ query, limit, threshold, error }), |
| 1394 | ); |
| 1395 | return []; |
| 1396 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 697 | req.body.method.startsWith('notifications/') |
| 698 | ) { |
| 699 | // Case 4: Session-less notification requests should be acknowledged and ignored |
| 700 | console.log( |
| 701 | `[SESSION SKIP] Ignoring session-less notification request (method: ${req.body.method})${username ? ` for user: ${username}` : ''}`, |
| 702 | ); |
| 703 | res.status(200).end(); |
| 704 | return; |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 78 | data: response.data, |
| 79 | }; |
| 80 | } catch (error) { |
| 81 | console.error('Error getting prompt', { promptName: request.promptName, server, error }); |
| 82 | return { |
| 83 | success: false, |
| 84 | error: error instanceof Error ? error.message : 'Unknown error occurred', |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 3 | export const checkLatestVersion = async (): Promise<string | null> => { |
| 4 | try { |
| 5 | const response = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`); |
| 6 | if (!response.ok) { |
| 7 | throw new Error(`Failed to fetch latest version: ${response.status}`); |
| 8 | } |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 84 | } |
| 85 | |
| 86 | // Make the actual fetch request |
| 87 | let response = await fetch(url, config); |
| 88 | |
| 89 | // Apply response interceptors |
| 90 | for (const interceptor of interceptors) { |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 27 | url.searchParams.append('search', search); |
| 28 | } |
| 29 | |
| 30 | const response = await fetch(url.toString(), { |
| 31 | headers: { |
| 32 | Accept: 'application/json, application/problem+json', |
| 33 | }, |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 193 | "inputSchema": { |
| 194 | "type": "object", |
| 195 | "properties": { |
| 196 | "path": { "type": "string", "description": "่ฆ่ฏปๅ็ๆไปถ่ทฏๅพ" }, |
| 197 | "encoding": { "type": "string", "default": "utf-8" } |
| 198 | }, |
| 199 | "required": ["path"] |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 502 | "inputSchema": { |
| 503 | "type": "object", |
| 504 | "properties": { |
| 505 | "path": { "type": "string", "description": "File path to read" }, |
| 506 | "encoding": { "type": "string", "default": "utf-8" } |
| 507 | }, |
| 508 | "required": ["path"] |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 228 | "inputSchema": { |
| 229 | "type": "object", |
| 230 | "properties": { |
| 231 | "path": { "type": "string", "description": "่ฆ่ฏปๅ็ๆไปถ่ทฏๅพ" }, |
| 232 | "encoding": { "type": "string", "default": "utf-8" } |
| 233 | }, |
| 234 | "required": ["path"] |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 537 | "inputSchema": { |
| 538 | "type": "object", |
| 539 | "properties": { |
| 540 | "path": { "type": "string", "description": "File path to read" }, |
| 541 | "encoding": { "type": "string", "default": "utf-8" } |
| 542 | }, |
| 543 | "required": ["path"] |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage โ e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | FROM python:3.13-slim-bookworm AS base |
| 2 | |
| 3 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ |
| 4 | |
| 5 | RUN apt-get update && apt-get install -y curl gnupg git build-essential \ |
| 6 | && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ |
| 7 | && apt-get install -y nodejs \ |
| 8 | && apt-get clean && rm -rf /var/lib/apt/lists/* |
| 9 | |
| 10 | RUN npm install -g pnpm |
| 11 | |
| 12 | ENV PNPM_HOME=/usr/local/share/pnpm |
| 13 | ENV PATH=$PNPM_HOME:$PATH |
| 14 | RUN mkdir -p $PNPM_HOME && \ |
| 15 | pnpm add -g @amap/amap-maps-mcp-server @playwright/mcp@latest t |
Remediation
Create and switch to a non-root user before the CMD / ENTRYPOINT: RUN adduser --system --uid 1000 app USER 1000 Or reuse the base image's shipped non-root account (e.g. `USER nobody`, `USER nonroot` on distroless). Multi-stage builds only need the USER directive in the final stage.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 88 | # steps: |
| 89 | # - name: Checkout code |
| 90 | # uses: actions/checkout@v4 |
| 91 | |
| 92 | # - name: Setup Node.js |
| 93 | # uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 15 | uses: actions/checkout@v4 |
| 16 | |
| 17 | - name: Release |
| 18 | uses: softprops/action-gh-release@v2 |
| 19 | with: |
| 20 | generate_release_notes: true |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 31 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT |
| 32 | |
| 33 | - name: Setup pnpm cache |
| 34 | uses: actions/cache@v4 |
| 35 | with: |
| 36 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} |
| 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 54 | # uses: actions/checkout@v4 |
| 55 | |
| 56 | # - name: Setup Node.js |
| 57 | # uses: actions/setup-node@v4 |
| 58 | # with: |
| 59 | # node-version: '20.x' |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 13 | uses: actions/checkout@v4 |
| 14 | |
| 15 | - name: Setup Node.js |
| 16 | uses: actions/setup-node@v4 |
| 17 | with: |
| 18 | node-version: '20' |
| 19 | registry-url: 'https://registry.npmjs.org' |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 49 | - name: Docker metadata |
| 50 | id: meta |
| 51 | uses: docker/metadata-action@v5 |
| 52 | with: |
| 53 | images: | |
| 54 | ${{ endsWith(github.repository, 'mcphub') && github.repository || '' }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 34 | - name: Login to Docker Hub |
| 35 | if: endsWith(github.repository, 'mcphub') |
| 36 | uses: docker/login-action@v3 |
| 37 | with: |
| 38 | username: ${{ secrets.DOCKER_USERNAME }} |
| 39 | password: ${{ secrets.DOCKER_PASSWORD }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 16 | steps: |
| 17 | - name: Checkout code |
| 18 | uses: actions/checkout@v4 |
| 19 | |
| 20 | - name: Setup Node.js ${{ matrix.node-version }} |
| 21 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 91 | # uses: actions/checkout@v4 |
| 92 | |
| 93 | # - name: Setup Node.js |
| 94 | # uses: actions/setup-node@v4 |
| 95 | # with: |
| 96 | # node-version: '20.x' |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 12 | runs-on: ubuntu-latest |
| 13 | steps: |
| 14 | - name: Checkout |
| 15 | uses: actions/checkout@v4 |
| 16 | |
| 17 | - name: Release |
| 18 | uses: softprops/action-gh-release@v2 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 30 | grep -m 1 "version" package.json |
| 31 | |
| 32 | - name: Set up Docker Buildx |
| 33 | uses: docker/setup-buildx-action@v3 |
| 34 | |
| 35 | - name: Login to Docker Hub |
| 36 | if: endsWith(github.repository, 'mcphub') |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 15 | matrix: |
| 16 | variant: ${{ startsWith(github.ref, 'refs/tags/') && fromJSON('["base", "full"]') || fromJSON('["base"]') }} |
| 17 | steps: |
| 18 | - uses: actions/checkout@v4 |
| 19 | with: |
| 20 | fetch-depth: 0 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 51 | # steps: |
| 52 | # - name: Checkout code |
| 53 | # uses: actions/checkout@v4 |
| 54 | |
| 55 | # - name: Setup Node.js |
| 56 | # uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 41 | - name: Login to GitHub Container Registry |
| 42 | if: endsWith(github.repository, 'mcphubx') |
| 43 | uses: docker/login-action@v3 |
| 44 | with: |
| 45 | registry: ghcr.io |
| 46 | username: ${{ github.actor }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 10 | if: endsWith(github.repository, 'mcphub') |
| 11 | steps: |
| 12 | - name: Checkout |
| 13 | uses: actions/checkout@v4 |
| 14 | |
| 15 | - name: Setup Node.js |
| 16 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | registry-url: 'https://registry.npmjs.org' |
| 20 | |
| 21 | - name: Setup pnpm |
| 22 | uses: pnpm/action-setup@v2 |
| 23 | with: |
| 24 | version: 10 |
| 25 | run_install: false |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | uses: actions/checkout@v4 |
| 20 | |
| 21 | - name: Setup Node.js ${{ matrix.node-version }} |
| 22 | uses: actions/setup-node@v4 |
| 23 | with: |
| 24 | node-version: ${{ matrix.node-version }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 39 | run: pnpm test:ci |
| 40 | |
| 41 | - name: Upload coverage to Codecov |
| 42 | uses: codecov/codecov-action@v4 |
| 43 | with: |
| 44 | file: ./coverage/lcov.info |
| 45 | flags: unittests |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 63 | - name: Build and Push Docker Image |
| 64 | if: endsWith(github.repository, 'mcphub') || endsWith(github.repository, 'mcphubx') |
| 65 | uses: docker/build-push-action@v5 |
| 66 | with: |
| 67 | context: . |
| 68 | push: ${{ github.event_name != 'pull_request' }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc