UNPKG

trufflehog-js

Version:

TypeScript wrapper for TruffleHog secret scanner

271 lines (235 loc) 6.56 kB
#!/usr/bin/env bun /** * Copyright (c) 2025 maloma7. All rights reserved. * SPDX-License-Identifier: MIT */ import type { ScanOptions, ScanResult } from "./types.ts"; import { ExitCode } from "./types.ts"; import { createLogger } from "./logger.ts"; import { TruffleHogScanner, throwIfSecretsFound } from "./scanner.ts"; import { handleProcessExit, TruffleHogError } from "./errors.ts"; interface CliOptions { staged: boolean; quiet: boolean; verbose: boolean; config?: string; exclude: string[]; timeout: number; verify?: boolean; includeDetectors: string[]; excludeDetectors: string[]; help: boolean; version: boolean; } function parseArgs(args: string[]): CliOptions { const options: CliOptions = { staged: false, quiet: false, verbose: false, exclude: [], timeout: 120000, // 2 minutes includeDetectors: [], excludeDetectors: [], help: false, version: false, }; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case "--staged": options.staged = true; break; case "--quiet": options.quiet = true; break; case "--verbose": options.verbose = true; break; case "--config": { const configArg = args[++i]; if (configArg !== undefined) { options.config = configArg; } break; } case "--exclude": { const excludeArg = args[++i]; if (excludeArg !== undefined) { options.exclude.push(excludeArg); } break; } case "--timeout": { const timeoutArg = args[++i]; if (timeoutArg !== undefined) { options.timeout = parseInt(timeoutArg, 10); } break; } case "--verify": options.verify = true; break; case "--no-verify": options.verify = false; break; case "--include-detectors": { const includeDetectorsArg = args[++i]; if (includeDetectorsArg !== undefined) { options.includeDetectors.push(...includeDetectorsArg.split(",")); } break; } case "--exclude-detectors": { const excludeDetectorsArg = args[++i]; if (excludeDetectorsArg !== undefined) { options.excludeDetectors.push(...excludeDetectorsArg.split(",")); } break; } case "--help": case "-h": options.help = true; break; case "--version": case "-v": options.version = true; break; default: if (arg?.startsWith("--")) { throw new TruffleHogError(`Unknown option: ${arg}`, ExitCode.ERROR); } break; } } return options; } function printHelp(): void { const help = ` TruffleHog-JS - TypeScript wrapper for TruffleHog secret scanner USAGE: bunx trufflehog-js [OPTIONS] OPTIONS: --staged Scan only staged files (default for pre-commit) --quiet Minimal output for commit hooks --verbose Detailed output for debugging --config <path> Custom configuration file --exclude <glob> Exclude file patterns (can be used multiple times) --timeout <ms> Maximum execution time (default: 120000) --verify Enable secret verification --no-verify Disable secret verification --include-detectors Comma-separated list of detectors to include --exclude-detectors Comma-separated list of detectors to exclude --help, -h Show this help message --version, -v Show version information EXAMPLES: # Scan staged files (typical pre-commit usage) bunx trufflehog-js --staged --quiet # Scan with verbose output bunx trufflehog-js --staged --verbose # Scan with custom timeout and excluded patterns bunx trufflehog-js --staged --timeout 60000 --exclude "*.test.ts" EXIT CODES: 0 No secrets found (success) 1 Secrets detected (commit should be blocked) 2 Tool error (commit should be blocked) LEFTHOOK INTEGRATION: Add to .lefthook.yml: pre-commit: commands: secrets: run: bunx trufflehog-js --staged --quiet fail_text: "🚨 Secrets detected! Please remove before committing." `; globalThis.console.log(help.trim()); } function printVersion(): void { globalThis.console.log("trufflehog-js v1.0.0"); } async function runScan(options: CliOptions): Promise<void> { const logger = createLogger({ quiet: options.quiet, verbose: options.verbose, level: options.verbose ? "debug" : options.quiet ? "error" : "warn", }); const scanOptions: ScanOptions = { staged: options.staged, quiet: options.quiet, verbose: options.verbose, timeout: options.timeout, ...(options.verify !== undefined ? { verify: options.verify } : {}), excludePaths: options.exclude, ...(options.includeDetectors.length > 0 ? { includeDetectors: options.includeDetectors } : {}), ...(options.excludeDetectors.length > 0 ? { excludeDetectors: options.excludeDetectors } : {}), }; const scanner = new TruffleHogScanner({ timeout: options.timeout, logger, }); let results: ScanResult[]; if (options.staged) { logger.debug("Scanning staged files..."); results = await scanner.scanStagedFiles(scanOptions); } else { // If not staged, we would need to implement scanning of all files // For V1, we focus on staged files only throw new TruffleHogError( "Non-staged scanning not implemented in V1. Use --staged flag.", ExitCode.ERROR, ); } if (results.length === 0) { if (!options.quiet) { logger.info("✅ No secrets detected"); } return; } // Secrets found - format output if (!options.quiet) { logger.error( `🚨 Found ${results.length} secret${results.length === 1 ? "" : "s"}:`, ); for (const result of results) { logger.error( ` ${result.detector} in ${result.file}${result.verified ? " (verified)" : ""}`, ); if (options.verbose) { logger.error(` Secret: ${result.secret}`); } } } throwIfSecretsFound(results); } async function main(): Promise<void> { try { const args = Bun.argv.slice(2); // Remove 'bun' and script path const options = parseArgs(args); if (options.help) { printHelp(); process.exit(ExitCode.SUCCESS); } if (options.version) { printVersion(); process.exit(ExitCode.SUCCESS); } // Default to staged scanning for pre-commit usage if (!options.staged) { options.staged = true; } await runScan(options); process.exit(ExitCode.SUCCESS); } catch (error) { const logger = createLogger({ quiet: false, verbose: Bun.env.TRUFFLEHOG_VERBOSE === "true", }); handleProcessExit(error as Error, logger); } } // Run the CLI if this file is executed directly if (import.meta.main) { await main(); }