UNPKG

@slathar-dev/mcp-sensitive-read

Version:

MCP server for secure file reading within project boundaries

206 lines (175 loc) 6.29 kB
#!/usr/bin/env node import fs from "node:fs/promises"; import path from "node:path"; import os from "node:os"; import { spawn } from "node:child_process"; import { createWriteStream } from "node:fs"; import { pipeline } from "node:stream/promises"; function getCacheDir() { const homeDir = os.homedir(); return path.join(homeDir, ".claude", ".mcp-sensitive-read", "bin"); } function getBinaryName() { const platform = process.platform; const arch = process.arch; if (platform === "win32") { return "gitleaks.exe"; } else if (platform === "darwin") { return arch === "arm64" ? "gitleaks-darwin-arm64" : "gitleaks-darwin-amd64"; } else if (platform === "linux") { return arch === "arm64" ? "gitleaks-linux-arm64" : "gitleaks-linux-amd64"; } else { throw new Error(`Unsupported platform: ${platform}-${arch}`); } } async function checkBinaryExists(binaryPath) { try { await fs.access(binaryPath, fs.constants.X_OK); // Test the binary works by running version command return new Promise((resolve) => { const child = spawn(binaryPath, ["version"], { stdio: ["ignore", "pipe", "pipe"] }); let stdout = ""; let stderr = ""; child.stdout?.on("data", (data) => { stdout += data.toString(); }); child.stderr?.on("data", (data) => { stderr += data.toString(); }); child.on("close", (code) => { if (code === 0 && (stdout.length > 0 || stderr.includes("8."))) { resolve(true); } else { resolve(false); } }); child.on("error", () => { resolve(false); }); }); } catch (error) { return false; } } async function downloadBinary() { const version = "v8.21.2"; const platform = process.platform; const arch = process.arch; const cacheDir = getCacheDir(); const binaryName = getBinaryName(); const binaryPath = path.join(cacheDir, binaryName); let downloadUrl; let extractedBinaryName; if (platform === "win32") { downloadUrl = `https://github.com/gitleaks/gitleaks/releases/download/${version}/gitleaks_${version.slice(1)}_windows_x64.zip`; extractedBinaryName = "gitleaks.exe"; } else if (platform === "darwin") { const archSuffix = arch === "arm64" ? "arm64" : "x64"; downloadUrl = `https://github.com/gitleaks/gitleaks/releases/download/${version}/gitleaks_${version.slice(1)}_darwin_${archSuffix}.tar.gz`; extractedBinaryName = "gitleaks"; } else if (platform === "linux") { const archSuffix = arch === "arm64" ? "arm64" : "x64"; downloadUrl = `https://github.com/gitleaks/gitleaks/releases/download/${version}/gitleaks_${version.slice(1)}_linux_${archSuffix}.tar.gz`; extractedBinaryName = "gitleaks"; } else { throw new Error(`Unsupported platform for download: ${platform}-${arch}`); } console.log(`Downloading Gitleaks ${version} to cache directory...`); console.log(`Cache directory: ${cacheDir}`); // Create cache directory await fs.mkdir(cacheDir, { recursive: true }); const response = await fetch(downloadUrl); if (!response.ok) { throw new Error(`Failed to download Gitleaks: ${response.statusText}`); } const tempFile = path.join(cacheDir, `gitleaks-download-${Date.now()}`); try { // Download to temporary file const fileStream = createWriteStream(tempFile); if (!response.body) { throw new Error("No response body"); } await pipeline(response.body, fileStream); // Extract the binary if (platform === "win32") { console.error("ZIP extraction not implemented - please install gitleaks manually on Windows"); return false; } else { await extractTarGz(tempFile, cacheDir, binaryPath); } // Make executable on Unix systems if (platform !== "win32") { await fs.chmod(binaryPath, 0o755); } console.log("Gitleaks binary downloaded and cached successfully"); return true; } finally { // Clean up temp file try { await fs.unlink(tempFile); } catch { // Ignore cleanup errors } } } async function extractTarGz(tarPath, targetDir, finalBinaryPath) { return new Promise((resolve, reject) => { const child = spawn("tar", ["-xzf", tarPath, "-C", targetDir], { stdio: ["ignore", "pipe", "pipe"] }); let stderr = ""; child.stderr?.on("data", (data) => { stderr += data.toString(); }); child.on("close", async (code) => { if (code === 0) { try { // Find the extracted gitleaks binary const files = await fs.readdir(targetDir); const gitleaksBinary = files.find(file => file === "gitleaks" || file === "gitleaks.exe"); if (gitleaksBinary) { const sourcePath = path.join(targetDir, gitleaksBinary); // Move/rename the binary to the expected location await fs.rename(sourcePath, finalBinaryPath); resolve(); } else { reject(new Error(`Gitleaks binary not found in extracted files: ${files.join(", ")}`)); } } catch (error) { reject(new Error(`Failed to locate extracted binary: ${error}`)); } } else { reject(new Error(`tar extraction failed with code ${code}: ${stderr}`)); } }); child.on("error", (error) => { reject(new Error(`tar extraction failed: ${error.message}`)); }); }); } async function main() { try { const cacheDir = getCacheDir(); const binaryName = getBinaryName(); const binaryPath = path.join(cacheDir, binaryName); // Check if binary already exists and works const exists = await checkBinaryExists(binaryPath); if (exists) { console.log("Gitleaks binary already cached and working"); return; } // Download and install the binary const success = await downloadBinary(); if (!success) { console.error("Failed to download Gitleaks binary"); process.exit(1); } } catch (error) { console.error("Postinstall failed:", error.message); console.error("Note: Gitleaks will still work if installed system-wide or via GITLEAKS_BINARY env var"); // Don't exit with error code to avoid breaking npm install } } main();