supamend
Version:
Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels
169 lines • 7.65 kB
JavaScript
;
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