UNPKG

trufflehog-js

Version:

TypeScript wrapper for TruffleHog secret scanner

364 lines (304 loc) 9.99 kB
#!/usr/bin/env bun /** * Copyright (c) 2025 maloma7. All rights reserved. * SPDX-License-Identifier: MIT * * Postinstall script to download platform-specific TruffleHog binary */ import { join } from "node:path"; const SUPPORTED_PLATFORMS = { "linux-x64": { platform: "linux", arch: "x64", binaryName: "trufflehog_3.90.8_linux_amd64.tar.gz", downloadUrl: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_linux_amd64.tar.gz", }, "linux-arm64": { platform: "linux", arch: "arm64", binaryName: "trufflehog_3.90.8_linux_arm64.tar.gz", downloadUrl: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_linux_arm64.tar.gz", }, "darwin-x64": { platform: "darwin", arch: "x64", binaryName: "trufflehog_3.90.8_darwin_amd64.tar.gz", downloadUrl: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_darwin_amd64.tar.gz", }, "darwin-arm64": { platform: "darwin", arch: "arm64", binaryName: "trufflehog_3.90.8_darwin_arm64.tar.gz", downloadUrl: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_darwin_arm64.tar.gz", }, "win32-x64": { platform: "win32", arch: "x64", binaryName: "trufflehog_3.90.8_windows_amd64.tar.gz", downloadUrl: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_windows_amd64.tar.gz", }, }; const CHECKSUMS_URL = "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_checksums.txt"; const CHECKSUMS_SIGNATURE_URL = "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_checksums.txt.sig"; const PUBLIC_KEY_URL = "https://github.com/trufflesecurity/trufflehog/releases/download/v3.90.8/trufflehog_3.90.8_checksums.txt.pem"; const MAX_RETRIES = 3; const RETRY_DELAY_MS = 1000; function getCurrentPlatform() { const platform = process.platform; const arch = process.arch; let normalizedPlatform; switch (platform) { case "linux": normalizedPlatform = "linux"; break; case "darwin": normalizedPlatform = "darwin"; break; case "win32": normalizedPlatform = "win32"; break; default: throw new Error(`Platform not supported: ${platform}`); } let normalizedArch; switch (arch) { case "x64": case "x86_64": case "amd64": normalizedArch = "x64"; break; case "arm64": case "aarch64": normalizedArch = "arm64"; break; default: throw new Error(`Architecture not supported: ${arch}`); } return `${normalizedPlatform}-${normalizedArch}`; } async function downloadWithRetry(url, targetPath, retryCount = 0) { try { console.log(`Downloading ${url}...`); const response = await fetch(url, { signal: AbortSignal.timeout(300000), // 5 minutes }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const arrayBuffer = await response.arrayBuffer(); await Bun.write(targetPath, arrayBuffer); console.log(`Downloaded to ${targetPath}`); } catch (error) { if (retryCount < MAX_RETRIES) { const delay = RETRY_DELAY_MS * 2 ** retryCount; console.warn( `Download failed, retrying in ${delay}ms (attempt ${retryCount + 1}/${MAX_RETRIES})`, ); await new Promise((resolve) => setTimeout(resolve, delay)); return downloadWithRetry(url, targetPath, retryCount + 1); } throw new Error(`Failed to download ${url}: ${error.message}`); } } async function downloadText(url) { try { const response = await fetch(url, { signal: AbortSignal.timeout(300000), // 5 minutes }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.text(); } catch (error) { throw new Error(`Failed to download from ${url}: ${error.message}`); } } async function downloadAndVerifyChecksums() { console.log("Downloading and verifying checksums..."); const [checksums, signature, publicKey] = await Promise.all([ downloadText(CHECKSUMS_URL), downloadText(CHECKSUMS_SIGNATURE_URL), downloadText(PUBLIC_KEY_URL), ]); await verifySignature(checksums, signature, publicKey); return checksums; } async function verifySignature(checksums, signature, publicKey) { try { if (!checksums || !signature || !publicKey) { throw new Error("Missing checksums, signature, or public key"); } console.log("Verifying cryptographic signature..."); // Create temporary files for OpenSSL verification const tempDir = `/tmp/trufflehog-verify-${Date.now()}`; await Bun.$`mkdir -p ${tempDir}`; const checksumFile = `${tempDir}/checksums.txt`; const signatureFile = `${tempDir}/checksums.txt.sig`; const publicKeyFile = `${tempDir}/checksums.txt.pem`; try { // Write files for OpenSSL await Bun.write(checksumFile, checksums); await Bun.write(publicKeyFile, publicKey); // Handle signature data (likely binary) const signatureBuffer = parseSignatureData(signature); await Bun.write(signatureFile, signatureBuffer); // Verify signature using OpenSSL const result = await Bun.$`openssl dgst -sha256 -verify ${publicKeyFile} -signature ${signatureFile} ${checksumFile}`.quiet(); if (result.exitCode !== 0) { const stderr = await new Response(result.stderr).text(); throw new Error(`OpenSSL signature verification failed: ${stderr}`); } console.log("✅ Cryptographic signature verified successfully"); } finally { // Cleanup temporary files await Bun.$`rm -rf ${tempDir}`.quiet(); } } catch (error) { throw new Error(`Signature verification failed: ${error.message}`); } } function parseSignatureData(signature) { // TruffleHog signatures might be base64 encoded or raw binary try { // First try as base64 if (signature.match(/^[A-Za-z0-9+/]+=*$/)) { const decoded = atob(signature); return new Uint8Array( Array.from(decoded).map((char) => char.charCodeAt(0)), ); } // If not base64, treat as raw binary string return new TextEncoder().encode(signature); } catch (error) { throw new Error(`Failed to parse signature data: ${error.message}`); } } function extractChecksum(checksums, binaryName) { const lines = checksums.split("\n"); for (const line of lines) { if (line.includes(binaryName)) { const parts = line.trim().split(/\s+/); if (parts.length >= 2) { return parts[0]; } } } throw new Error(`Checksum not found for ${binaryName}`); } async function verifyChecksum(filePath, expectedChecksum) { try { const file = Bun.file(filePath); const content = await file.arrayBuffer(); const hasher = new Bun.CryptoHasher("sha256"); hasher.update(content); const actualChecksum = hasher.digest("hex"); if (actualChecksum !== expectedChecksum.toLowerCase()) { throw new Error( `Checksum mismatch: expected ${expectedChecksum}, got ${actualChecksum}`, ); } console.log("Checksum verified successfully"); } catch (error) { throw new Error(`Checksum verification failed: ${error.message}`); } } async function extractBinary(archivePath, targetPath, platformInfo) { try { const binDir = join(process.cwd(), "bin"); await Bun.$`mkdir -p ${binDir}`; if (!platformInfo.binaryName.endsWith(".tar.gz")) { // Direct binary await Bun.$`mv ${archivePath} ${targetPath}`; await Bun.$`chmod +x ${targetPath}`; return; } // Extract tar.gz const tempDir = `${targetPath}.extract`; await Bun.$`mkdir -p ${tempDir}`; try { await Bun.$`tar -xzf ${archivePath} -C ${tempDir}`; const binaryName = platformInfo.platform === "win32" ? "trufflehog.exe" : "trufflehog"; const extractedBinary = join(tempDir, binaryName); await Bun.$`mv ${extractedBinary} ${targetPath}`; await Bun.$`chmod +x ${targetPath}`; console.log(`Extracted binary to ${targetPath}`); } finally { await Bun.$`rm -rf ${tempDir}`; } } catch (error) { throw new Error(`Binary extraction failed: ${error.message}`); } } async function cleanup(path) { try { await Bun.$`rm -rf ${path}`; } catch { // Ignore cleanup errors } } async function main() { try { // Skip download if TRUFFLEHOG_BINARY_PATH is set if (process.env.TRUFFLEHOG_BINARY_PATH) { console.log("TRUFFLEHOG_BINARY_PATH is set, skipping binary download"); return; } const platformKey = getCurrentPlatform(); const platformInfo = SUPPORTED_PLATFORMS[platformKey]; if (!platformInfo) { console.warn( `Platform ${platformKey} not supported. Set TRUFFLEHOG_BINARY_PATH to use custom binary.`, ); return; } console.log(`Installing TruffleHog for ${platformKey}...`); // Download and verify checksums first const checksums = await downloadAndVerifyChecksums(); const expectedChecksum = extractChecksum( checksums, platformInfo.binaryName, ); // Setup paths const binDir = join(process.cwd(), "bin"); const binaryName = platformInfo.platform === "win32" ? "trufflehog.exe" : "trufflehog"; const targetPath = join(binDir, binaryName); const tempPath = `${targetPath}.tmp`; // Ensure bin directory exists await Bun.$`mkdir -p ${binDir}`; try { // Download binary await downloadWithRetry(platformInfo.downloadUrl, tempPath); // Verify checksum await verifyChecksum(tempPath, expectedChecksum); // Extract binary await extractBinary(tempPath, targetPath, platformInfo); // Cleanup await cleanup(tempPath); console.log("✅ TruffleHog binary installed successfully"); } catch (error) { await cleanup(tempPath); throw error; } } catch (error) { console.error("❌ Failed to install TruffleHog binary:"); console.error(error.message); console.log("\nYou can manually specify a binary path using:"); console.log("export TRUFFLEHOG_BINARY_PATH=/path/to/trufflehog"); // Don't fail the installation - allow manual setup process.exit(0); } } if (import.meta.main) { await main(); }