gitleaks-secret-scanner
Version:
A powerful, intelligent wrapper for the Gitleaks engine that provides accurate and safe secret scanning for local pre-commit hooks and CI/CD pipelines.
146 lines (125 loc) • 4.69 kB
JavaScript
const fs = require('fs');
const path = require('path');
const os = require('os');
const https = require('https');
const tar = require('tar');
const unzipper = require('unzipper');
let packageInfo = {
name: 'gitleaks-secret-scanner',
version: '1.2.2',
repository: { url: 'https://github.com/criisv7/gitleaks-secret-scanner' }
};
try {
const packagePath = path.join(__dirname, '..', 'package.json');
if (fs.existsSync(packagePath)) {
packageInfo = require(packagePath);
}
} catch (e) {
console.warn('⚠️ Error loading package.json:', e.message);
}
const CACHE_DIR = path.join(os.homedir(), '.gitleaks-cache');
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
module.exports.installGitleaks = async (config) => {
const platform = os.platform();
const arch = os.arch();
const binaryName = platform === 'win32' ? 'gitleaks.exe' : 'gitleaks';
let version = config.version || '8.27.2';
console.log('Using Gitleaks version:', version);
const versionDir = path.join(CACHE_DIR, `v${version}`);
const binaryPath = path.join(versionDir, binaryName);
if (fs.existsSync(binaryPath)) {
return binaryPath;
}
if (!fs.existsSync(versionDir)) {
fs.mkdirSync(versionDir, { recursive: true });
}
try {
const fileName = getFileName(version, platform, arch);
const downloadUrl = `https://github.com/gitleaks/gitleaks/releases/download/v${version}/${fileName}`;
console.log('Downloading from:', downloadUrl);
await downloadAndExtract(downloadUrl, versionDir, platform);
if (platform !== 'win32') {
fs.chmodSync(binaryPath, 0o755);
}
console.log('✅ Gitleaks binary successfully installed.');
return binaryPath;
} catch (error) {
console.error(`❌ Failed to download or extract Gitleaks: ${error.message}`);
if (error.message.includes('404')) {
console.error(`Could not find the Gitleaks binary for your system (platform: ${platform}, arch: ${arch}).`);
}
throw error;
}
};
function getFileName(version, platform, arch) {
let osName, archName;
switch (platform) {
case 'darwin': osName = 'darwin'; break;
case 'linux': osName = 'linux'; break;
case 'win32': osName = 'windows'; break;
default: throw new Error(`Unsupported platform: ${platform}`);
}
switch (arch) {
case 'x64':
archName = 'x64';
break;
case 'arm64':
archName = 'arm64';
break;
case 'arm':
// Node.js doesn't easily distinguish between armv6 and armv7.
// default to the more common armv7 but warn the user.
archName = 'armv7';
console.warn(`⚠️ Detected 'arm' architecture. Assuming 'armv7'. If you need 'armv6', this may fail.`);
break;
case 'ia32':
// This case handles both 'linux_x32' and 'windows_x32'.
archName = 'x32';
break;
default:
throw new Error(`Unsupported architecture: ${arch}.`);
}
const ext = (osName === 'windows') ? 'zip' : 'tar.gz';
const fileName = `gitleaks_${version}_${osName}_${archName}.${ext}`;
console.log(`Generated file name for download: ${fileName}`);
return fileName;
}
async function downloadAndExtract(url, targetDir, platform) {
return new Promise((resolve, reject) => {
console.log('Starting download...');
const headers = { 'User-Agent': `${packageInfo.name}/${packageInfo.version}` };
const request = https.get(url, { headers }, response => {
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
console.log('Redirecting to:', response.headers.location);
downloadAndExtract(response.headers.location, targetDir, platform).then(resolve).catch(reject);
return;
}
if (response.statusCode !== 200) {
response.resume();
return reject(new Error(`Download failed with status code: ${response.statusCode}`));
}
let extractor;
if (platform === 'win32') {
console.log('Using ZIP extractor for Windows...');
extractor = unzipper.Extract({ path: targetDir });
} else {
console.log('Using TAR extractor for macOS/Linux...');
extractor = tar.x({ C: targetDir });
}
response.pipe(extractor)
.on('finish', () => {
console.log('Extraction complete.');
resolve();
})
.on('error', (err) => {
const archiveType = platform === 'win32' ? 'ZIP' : 'TAR';
reject(new Error(`${archiveType} extraction failed: ${err.message}`));
});
});
request.on('error', (err) => {
reject(new Error(`Download request failed: ${err.message}`));
});
});
}