trufflehog-js
Version:
TypeScript wrapper for TruffleHog secret scanner
364 lines (304 loc) • 9.99 kB
JavaScript
/**
* 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();
}