@slathar-dev/mcp-sensitive-read
Version:
MCP server for secure file reading within project boundaries
206 lines (175 loc) • 6.29 kB
JavaScript
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();