Skip to main content
Glama
index.ts16 kB
#!/usr/bin/env node /** * Security Scanner MCP Server * * AI가 생성한 코드의 보안 취약점을 자동으로 검출하는 MCP 서버입니다. * OWASP Top 10, 하드코딩된 시크릿, 의존성 취약점 등을 검사합니다. * * @author zerry * @license MIT */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import * as z from 'zod'; import { scanSecrets } from './scanners/secrets.js'; import { scanInjection } from './scanners/injection.js'; import { scanXss } from './scanners/xss.js'; import { scanDependencies } from './scanners/dependencies.js'; import { scanCrypto } from './scanners/crypto.js'; import { scanAuth } from './scanners/auth.js'; import { scanPath } from './scanners/path.js'; import { SecurityIssue, ScanResult, Severity } from './types.js'; // ============================================ // MCP 서버 초기화 // ============================================ const server = new McpServer({ name: 'security-scanner', version: '1.0.0', }); /** * 전체 보안 스캔 실행 * * 코드 조각이나 파일 경로를 받아서 모든 보안 검사를 수행합니다. * 하드코딩된 시크릿, SQL Injection, XSS 등을 종합적으로 검사해요. */ server.registerTool( 'scan-security', { title: '보안 취약점 스캔', description: '코드에서 보안 취약점을 검사합니다 (시크릿, SQL Injection, XSS 등)', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto') .describe('프로그래밍 언어 (auto면 자동 감지)'), context: z.string().optional().describe('추가 컨텍스트 (예: "API 엔드포인트 코드")'), }, }, async ({ code, language, context }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues: SecurityIssue[] = []; // 각종 보안 스캐너 실행 (OWASP Top 10 기반) issues.push(...scanSecrets(code)); // 하드코딩된 시크릿 issues.push(...scanInjection(code, detectedLang)); // SQL/Command Injection issues.push(...scanXss(code, detectedLang)); // XSS issues.push(...scanCrypto(code, detectedLang)); // 암호화 취약점 issues.push(...scanAuth(code, detectedLang)); // 인증/세션 취약점 issues.push(...scanPath(code, detectedLang)); // 파일/경로 취약점 // 결과 정리 const result = formatScanResult(issues, code, context); return { content: [{ type: 'text', text: result }] }; } ); /** * 하드코딩된 시크릿 검사 * * API 키, 비밀번호, 토큰 등이 코드에 직접 박혀있는지 검사합니다. * 이거 진짜 많이 실수하는 부분이에요... 저도 가끔 깜빡하고 커밋했다가 식겁한 적 있음 ㅋㅋ */ server.registerTool( 'scan-secrets', { title: '하드코딩된 시크릿 검사', description: 'API 키, 비밀번호, 토큰 등 민감한 정보가 코드에 노출되어 있는지 검사', inputSchema: { code: z.string().describe('검사할 코드'), }, }, async ({ code }) => { const issues = scanSecrets(code); const result = formatSecretScanResult(issues, code); return { content: [{ type: 'text', text: result }] }; } ); /** * SQL Injection 취약점 검사 * * 사용자 입력이 SQL 쿼리에 직접 들어가는 위험한 패턴을 찾습니다. * prepared statement 안 쓰면 진짜 큰일나요... */ server.registerTool( 'scan-injection', { title: 'SQL Injection 검사', description: 'SQL Injection 취약점이 있는지 검사', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto'), }, }, async ({ code, language }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues = scanInjection(code, detectedLang); const result = formatInjectionScanResult(issues, code); return { content: [{ type: 'text', text: result }] }; } ); /** * XSS 취약점 검사 * * 사용자 입력이 이스케이프 없이 HTML에 들어가는 패턴을 찾습니다. * React는 기본적으로 안전하지만 dangerouslySetInnerHTML 쓰면... */ server.registerTool( 'scan-xss', { title: 'XSS 취약점 검사', description: 'Cross-Site Scripting 취약점이 있는지 검사', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto'), }, }, async ({ code, language }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues = scanXss(code, detectedLang); const result = formatXssScanResult(issues, code); return { content: [{ type: 'text', text: result }] }; } ); /** * 의존성 취약점 검사 * * package.json이나 requirements.txt를 분석해서 알려진 취약점이 있는 패키지를 찾습니다. * npm audit이나 pip-audit 같은 역할이에요. */ server.registerTool( 'scan-dependencies', { title: '의존성 취약점 검사', description: 'package.json, requirements.txt 등에서 취약한 의존성 검사', inputSchema: { projectPath: z.string().describe('프로젝트 루트 경로'), }, }, async ({ projectPath }) => { const issues = await scanDependencies(projectPath); const result = formatDependencyScanResult(issues); return { content: [{ type: 'text', text: result }] }; } ); /** * 암호화 관련 취약점 검사 * * 약한 해시, 안전하지 않은 랜덤, 하드코딩된 암호화 키 등을 검사합니다. */ server.registerTool( 'scan-crypto', { title: '암호화 취약점 검사', description: '약한 해시, 안전하지 않은 랜덤, SSL/TLS 설정 등 검사', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto'), }, }, async ({ code, language }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues = scanCrypto(code, detectedLang); return { content: [{ type: 'text', text: formatCryptoScanResult(issues) }] }; } ); /** * 인증/세션 취약점 검사 * * JWT 설정, 세션 관리, CORS 설정 등을 검사합니다. */ server.registerTool( 'scan-auth', { title: '인증/세션 취약점 검사', description: 'JWT, 세션, CORS, 쿠키 설정 등 검사', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto'), }, }, async ({ code, language }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues = scanAuth(code, detectedLang); return { content: [{ type: 'text', text: formatAuthScanResult(issues) }] }; } ); /** * 파일/경로 취약점 검사 * * Path Traversal, 위험한 파일 작업, 파일 업로드 취약점 등을 검사합니다. */ server.registerTool( 'scan-path', { title: '파일/경로 취약점 검사', description: 'Path Traversal, 파일 업로드, 경로 조작 등 검사', inputSchema: { code: z.string().describe('검사할 코드'), language: z.enum(['javascript', 'typescript', 'python', 'java', 'go', 'auto']) .default('auto'), }, }, async ({ code, language }) => { const detectedLang = language === 'auto' ? detectLanguage(code) : language; const issues = scanPath(code, detectedLang); return { content: [{ type: 'text', text: formatPathScanResult(issues) }] }; } ); // ============================================ // 유틸리티 함수들 // ============================================ /** * 코드에서 언어를 자동 감지 * 완벽하진 않지만 대부분의 경우 잘 동작해요 */ function detectLanguage(code: string): string { if (code.includes('import React') || code.includes('useState')) return 'javascript'; if (code.includes(': string') || code.includes(': number')) return 'typescript'; if (code.includes('def ') || code.includes('import ')) return 'python'; if (code.includes('public class') || code.includes('private void')) return 'java'; if (code.includes('func ') || code.includes('package main')) return 'go'; return 'javascript'; // 기본값 } /** * 전체 스캔 결과 포맷팅 */ function formatScanResult(issues: SecurityIssue[], code: string, context?: string): string { if (issues.length === 0) { return `## ✅ 보안 검사 통과! 이 코드에서 발견된 보안 취약점이 없습니다. ${context ? `> 컨텍스트: ${context}` : ''} 하지만 자동 검사로 모든 취약점을 찾을 수는 없어요. 중요한 코드는 수동 보안 리뷰도 권장합니다.`; } const critical = issues.filter(i => i.severity === 'critical'); const high = issues.filter(i => i.severity === 'high'); const medium = issues.filter(i => i.severity === 'medium'); const low = issues.filter(i => i.severity === 'low'); let result = `## ⚠️ 보안 취약점 발견! 총 **${issues.length}개**의 취약점이 발견되었습니다. `; if (critical.length > 0) { result += `### 🔴 Critical (${critical.length}개)\n`; result += formatIssueList(critical); } if (high.length > 0) { result += `### 🟠 High (${high.length}개)\n`; result += formatIssueList(high); } if (medium.length > 0) { result += `### 🟡 Medium (${medium.length}개)\n`; result += formatIssueList(medium); } if (low.length > 0) { result += `### 🟢 Low (${low.length}개)\n`; result += formatIssueList(low); } result += `\n---\n\n**이 취약점들을 수정한 후 다시 검사해주세요!**`; return result; } function formatIssueList(issues: SecurityIssue[]): string { return issues.map(issue => ` - **${issue.type}** (라인 ${issue.line || '?'}) - ${issue.message} - 💡 해결책: ${issue.fix} `).join('') + '\n'; } function formatSecretScanResult(issues: SecurityIssue[], code: string): string { if (issues.length === 0) { return `## ✅ 하드코딩된 시크릿 없음! 코드에서 API 키, 비밀번호, 토큰 등이 발견되지 않았습니다. 환경 변수를 잘 사용하고 계시네요! 👍`; } return `## 🚨 하드코딩된 시크릿 발견! **${issues.length}개**의 민감한 정보가 코드에 노출되어 있습니다. ${issues.map(issue => ` ### ${issue.severity === 'critical' ? '🔴' : '🟠'} ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **내용**: \`${issue.match?.slice(0, 20)}...\` - **문제**: ${issue.message} - **해결책**: ${issue.fix} `).join('\n')} --- ⚠️ **절대로 이 코드를 커밋하지 마세요!** 시크릿은 환경 변수나 시크릿 매니저를 사용해주세요.`; } function formatInjectionScanResult(issues: SecurityIssue[], code: string): string { if (issues.length === 0) { return `## ✅ SQL Injection 취약점 없음! 안전한 쿼리 패턴을 사용하고 계시네요. Prepared Statement, ORM 등을 잘 활용하고 계십니다! 👍`; } return `## 🚨 SQL Injection 취약점 발견! **${issues.length}개**의 잠재적 SQL Injection 포인트가 있습니다. ${issues.map(issue => ` ### 🔴 ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **문제**: ${issue.message} - **위험**: 공격자가 임의의 SQL을 실행할 수 있습니다 - **해결책**: ${issue.fix} `).join('\n')} --- 📚 **참고**: OWASP SQL Injection Prevention Cheat Sheet`; } function formatXssScanResult(issues: SecurityIssue[], code: string): string { if (issues.length === 0) { return `## ✅ XSS 취약점 없음! 사용자 입력을 안전하게 처리하고 계십니다! 👍`; } return `## 🚨 XSS 취약점 발견! **${issues.length}개**의 잠재적 XSS 포인트가 있습니다. ${issues.map(issue => ` ### 🔴 ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **문제**: ${issue.message} - **위험**: 공격자가 악성 스크립트를 실행할 수 있습니다 - **해결책**: ${issue.fix} `).join('\n')}`; } function formatDependencyScanResult(issues: SecurityIssue[]): string { if (issues.length === 0) { return `## ✅ 의존성 취약점 없음! 사용 중인 패키지들에 알려진 취약점이 없습니다! 👍`; } return `## 🚨 취약한 의존성 발견! **${issues.length}개**의 취약한 패키지가 있습니다. ${issues.map(issue => ` ### ${issue.severity === 'critical' ? '🔴' : issue.severity === 'high' ? '🟠' : '🟡'} ${issue.type} - **패키지**: ${issue.match} - **문제**: ${issue.message} - **해결책**: ${issue.fix} `).join('\n')} \`npm audit fix\` 또는 \`npm update\`를 실행해보세요.`; } function formatCryptoScanResult(issues: SecurityIssue[]): string { if (issues.length === 0) { return `## ✅ 암호화 취약점 없음! 안전한 암호화 패턴을 사용하고 계시네요! 👍`; } return `## 🚨 암호화 취약점 발견! **${issues.length}개**의 암호화 관련 취약점이 있습니다. ${issues.map(issue => ` ### ${issue.severity === 'critical' ? '🔴' : issue.severity === 'high' ? '🟠' : '🟡'} ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **문제**: ${issue.message} - **해결책**: ${issue.fix} `).join('\n')}`; } function formatAuthScanResult(issues: SecurityIssue[]): string { if (issues.length === 0) { return `## ✅ 인증/세션 취약점 없음! 인증 관련 설정이 안전해 보입니다! 👍`; } return `## 🚨 인증/세션 취약점 발견! **${issues.length}개**의 인증 관련 취약점이 있습니다. ${issues.map(issue => ` ### ${issue.severity === 'critical' ? '🔴' : issue.severity === 'high' ? '🟠' : '🟡'} ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **문제**: ${issue.message} - **해결책**: ${issue.fix} `).join('\n')}`; } function formatPathScanResult(issues: SecurityIssue[]): string { if (issues.length === 0) { return `## ✅ 파일/경로 취약점 없음! 파일 처리가 안전해 보입니다! 👍`; } return `## 🚨 파일/경로 취약점 발견! **${issues.length}개**의 파일/경로 관련 취약점이 있습니다. ${issues.map(issue => ` ### ${issue.severity === 'critical' ? '🔴' : issue.severity === 'high' ? '🟠' : '🟡'} ${issue.type} - **위치**: 라인 ${issue.line || '?'} - **문제**: ${issue.message} - **해결책**: ${issue.fix} `).join('\n')}`; } // ============================================ // 서버 시작 // ============================================ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // stderr로 로그 출력 (stdout은 MCP 통신용) console.error('🔒 Security Scanner MCP Server 시작됨!'); console.error(' by zerry'); } main().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ongjin/security-scanner-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server