UNPKG

supamend

Version:

Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels

169 lines 7.65 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHubIssueReporter = void 0; const axios_1 = __importDefault(require("axios")); class GitHubIssueReporter { constructor() { this.name = 'github-issue'; this.description = 'Create GitHub issues for security findings'; this.version = '1.0.0'; this.baseUrl = 'https://api.github.com'; } async init(config) { this.token = config?.token || process.env.GITHUB_TOKEN; this.owner = config?.owner; this.repo = config?.repo; if (!this.token) { throw new Error('GitHub token is required. Set GITHUB_TOKEN environment variable or provide in config.'); } } async report(results, options) { if (!this.token) { throw new Error('GitHub token not configured'); } // Extract owner and repo from options if not set in config let owner = this.owner || options?.owner || process.env.GITHUB_OWNER; let repo = this.repo || options?.repo || process.env.GITHUB_REPO; // If repo is a GitHub URL, extract owner and repo name const repoUrl = options?.repo; if (repoUrl && repoUrl.includes('github.com')) { const match = repoUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/); if (match) { owner = owner || match[1]; repo = match[2].replace(/\.git$/, ''); } } if (!owner || !repo) { console.error('GitHub configuration missing:', { owner, repo, hasToken: !!this.token }); throw new Error('GitHub owner and repo are required. Set GITHUB_OWNER and GITHUB_REPO environment variables or provide in config.'); } console.log(`Creating GitHub issues for ${owner}/${repo} with ${results.length} findings`); console.log(`Extracted from URL: owner=${owner}, repo=${repo}`); // Group results by severity const groupedResults = this.groupResultsBySeverity(results); // Create issues for each severity level for (const [severity, severityResults] of Object.entries(groupedResults)) { if (severityResults.length > 0) { try { await this.createIssue(owner, repo, severity, severityResults); } catch (error) { console.error(`Failed to create ${severity} issue:`, error instanceof Error ? error.message : error); throw error; } } } } async isAvailable() { const hasToken = !!(this.token || process.env.GITHUB_TOKEN); if (!hasToken) { console.warn('GitHub Issue reporter not available: Missing GITHUB_TOKEN environment variable'); } return hasToken; } async createIssue(owner, repo, severity, results) { const issue = { title: `🔒 Security Scan: ${results.length} ${severity} severity issues found`, body: this.generateIssueBody(severity, results), labels: ['security', 'supamend', severity] }; try { console.log(`Creating GitHub issue for ${severity} severity (${results.length} issues)`); const response = await axios_1.default.post(`${this.baseUrl}/repos/${owner}/${repo}/issues`, issue, { headers: { 'Authorization': `Bearer ${this.token}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'SupaMend/1.0.0' }, timeout: 30000, httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) }); if (response.data?.number) { console.log(`✅ Created GitHub issue #${response.data.number} for ${severity} severity issues`); } else { console.error('GitHub API response missing issue number:', response.data); } } catch (error) { if (axios_1.default.isAxiosError(error)) { console.error(`GitHub API Error (${error.response?.status}):`, { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, url: `${this.baseUrl}/repos/${owner}/${repo}/issues` }); if (error.response?.status === 401) { throw new Error('GitHub authentication failed. Check your token permissions.'); } else if (error.response?.status === 404) { throw new Error(`Repository ${owner}/${repo} not found or no access.`); } else if (error.response?.status === 403) { throw new Error('GitHub API rate limit exceeded or insufficient permissions.'); } } throw new Error(`Failed to create GitHub issue: ${error instanceof Error ? error.message : error}`); } } generateIssueBody(severity, results) { let body = `## Security Scan Results - ${severity.toUpperCase()} Severity\n\n`; body += `Found ${results.length} security issues with ${severity} severity.\n\n`; // Group by scanner const byScanner = {}; for (const result of results) { if (result && result.scanner) { if (!byScanner[result.scanner]) { byScanner[result.scanner] = []; } byScanner[result.scanner].push(result); } } for (const [scanner, scannerResults] of Object.entries(byScanner)) { if (scannerResults && scannerResults.length > 0) { body += `### ${scanner.toUpperCase()}\n\n`; for (const result of scannerResults) { if (result?.title && result?.severity && result?.type && result?.description) { body += `#### ${result.title}\n`; body += `- **Severity**: ${result.severity}\n`; body += `- **Type**: ${result.type}\n`; body += `- **Description**: ${result.description}\n`; if (result.file) { body += `- **File**: \`${result.file}\``; if (result.line && result.line > 0) { body += `:${result.line}`; } body += '\n'; } if (result.rule) { body += `- **Rule**: ${result.rule}\n`; } body += '\n'; } } } } body += `---\n*Reported by SupaMend Security Scanner*`; return body; } groupResultsBySeverity(results) { const grouped = {}; for (const result of results) { if (result && result.severity) { if (!grouped[result.severity]) { grouped[result.severity] = []; } grouped[result.severity].push(result); } } return grouped; } } exports.GitHubIssueReporter = GitHubIssueReporter; exports.default = new GitHubIssueReporter(); //# sourceMappingURL=github-issue.js.map