supamend
Version:
Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels
165 lines • 6.95 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitleaksScanner = void 0;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
class GitleaksScanner {
constructor() {
this.name = 'gitleaks';
this.description = 'Detect hardcoded secrets and credentials using Gitleaks';
this.version = '1.0.0';
}
async init(config) {
// Check if gitleaks is available
const isAvailable = await this.isAvailable();
if (!isAvailable) {
throw new Error('Gitleaks is not installed. Please install it from https://github.com/zricethezav/gitleaks');
}
}
async scan(repoPath, options) {
return new Promise((resolve, reject) => {
const results = [];
// Prepare gitleaks command
const args = [
'detect',
'--source', repoPath,
'--report-format', 'json',
'--report-path', path.join(repoPath, 'gitleaks-report.json'),
'--no-git'
];
// Add custom rules if provided
if (options?.rules) {
args.push('--config', options.rules);
}
const gitleaks = (0, child_process_1.spawn)('gitleaks', args);
let stdout = '';
let stderr = '';
gitleaks.stdout.on('data', (data) => {
stdout += data.toString();
});
gitleaks.stderr.on('data', (data) => {
stderr += data.toString();
});
gitleaks.on('close', async (code) => {
try {
if (code === 0 || code === 1) { // Gitleaks returns 1 when secrets are found
// Try to read the JSON report
const reportPath = path.join(repoPath, 'gitleaks-report.json');
if (await fs.pathExists(reportPath)) {
const reportContent = await fs.readFile(reportPath, 'utf-8');
const report = JSON.parse(reportContent);
for (const finding of report) {
const result = {
id: `${this.name}-${finding.RuleID}-${finding.StartLine}`,
type: 'secret',
severity: this.mapSeverity(finding.RuleID),
title: `Hardcoded Secret: ${finding.RuleID}`,
description: finding.Description || `Found ${finding.RuleID} in ${finding.File}`,
file: finding.File,
line: finding.StartLine,
column: finding.StartColumn,
rule: finding.RuleID,
scanner: this.name,
timestamp: new Date(),
metadata: {
match: finding.Match,
secret: finding.Secret,
commit: finding.Commit
}
};
results.push(result);
}
// Clean up report file
await fs.remove(reportPath);
}
}
resolve(results);
}
catch (error) {
reject(new Error(`Failed to parse gitleaks results: ${error}`));
}
});
gitleaks.on('error', (error) => {
reject(new Error(`Gitleaks execution failed: ${error.message}`));
});
});
}
async isAvailable() {
return new Promise((resolve) => {
const gitleaks = (0, child_process_1.spawn)('gitleaks', ['version'], { stdio: 'pipe' });
let hasOutput = false;
gitleaks.stdout.on('data', () => {
hasOutput = true;
});
gitleaks.stderr.on('data', () => {
hasOutput = true;
});
gitleaks.on('close', (code) => {
resolve(code === 0 || hasOutput);
});
gitleaks.on('error', (err) => {
console.error('Gitleaks spawn error:', err);
resolve(false);
});
// Timeout after 5 seconds
setTimeout(() => {
gitleaks.kill();
resolve(false);
}, 5000);
});
}
mapSeverity(ruleId) {
const criticalRules = ['aws-access-key', 'aws-secret-key', 'private-key', 'password'];
const highRules = ['api-key', 'token', 'secret'];
const mediumRules = ['email', 'url'];
if (criticalRules.some(rule => ruleId.toLowerCase().includes(rule))) {
return 'critical';
}
else if (highRules.some(rule => ruleId.toLowerCase().includes(rule))) {
return 'high';
}
else if (mediumRules.some(rule => ruleId.toLowerCase().includes(rule))) {
return 'medium';
}
else {
return 'low';
}
}
}
exports.GitleaksScanner = GitleaksScanner;
exports.default = new GitleaksScanner();
//# sourceMappingURL=gitleaks.js.map