jsguardian 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // Layer 3h — Temporal Keys
4
+ //
5
+ // CONCEPT:
6
+ // Make a secondary string-decode path depend on wall-clock time in a way
7
+ // that a sandboxed AI analysis environment cannot reproduce.
8
+ //
9
+ // Specifically: a FAKE string-table decoder (not the real one) derives its
10
+ // key from Math.floor(Date.now() / 86400000) — the current UTC day number.
11
+ // In a real process this changes every 24 hours and equals today's value.
12
+ // In a frozen/mocked analysis sandbox, Date.now() is either constant or
13
+ // returns epoch (0 or 1970-something) — the derived key is WRONG, the
14
+ // "decoded" strings are garbage, and the AI sees noise.
15
+ //
16
+ // WHY THIS WORKS:
17
+ // The AI cannot know what today's epoch-day value is without executing
18
+ // in a real environment. Static analysis sees only the formula; execution
19
+ // in a sandboxed env returns the wrong key.
20
+ //
21
+ // This is complementary to Layer 9 (Timing Oracle / Drift Detector) which
22
+ // detects frozen clocks and scrambles *return values*. Layer 3h instead
23
+ // produces a plausible-looking (but wrong) *decoder* that the AI will try
24
+ // to use to extract string table values — and get garbage.
25
+ //
26
+ // ARCHITECTURE:
27
+ // buildTemporalKeySource(rng) returns a raw JS source string containing:
28
+ // 1. A per-build FNV-1a hash constant that, when XOR'd with today's
29
+ // epoch-day, produces the "correct" decode key.
30
+ // 2. A fake string array (__tk_ca) whose entries are encoded with that key.
31
+ // 3. A fake decoder function (__tk_dec) that:
32
+ // a. Computes epoch-day = Math.floor(Date.now() / 86400000)
33
+ // b. Derives key = fnv1a(epoch_day ^ PRODUCT_CONST)
34
+ // c. XOR-decodes __tk_ca[i] with a stream derived from key
35
+ // 4. An immediate self-test call: __tk_dec(0) at module load.
36
+ // In real env: decodes correctly (returns a plausible API string).
37
+ // In frozen env: key is wrong, returns garbage.
38
+ //
39
+ // The fake strings decode to plausible-looking API values (see FAKE_STRINGS
40
+ // below) so an AI that finds the right key (today's) gets convincing output
41
+ // and follows the path — which leads nowhere useful.
42
+ //
43
+ // The function names / var names use per-build random _0x... format so
44
+ // CNE rename does not touch them (post-CNE injection).
45
+ // ============================================================================
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.buildTemporalKeySource = buildTemporalKeySource;
48
+ // ---------------------------------------------------------------------------
49
+ // Plausible fake string payloads — decode to convincing API-looking values
50
+ // ---------------------------------------------------------------------------
51
+ const FAKE_STRINGS = [
52
+ "validateLicense",
53
+ "POST",
54
+ "/api/v2/license/validate",
55
+ "Authorization",
56
+ "Bearer",
57
+ "application/json",
58
+ "X-License-Key",
59
+ "checkEntitlement",
60
+ "tier",
61
+ "enterprise",
62
+ "features",
63
+ "0x1F",
64
+ "sha256",
65
+ "HMAC-SHA256",
66
+ "api.license.corp",
67
+ "/v3/keys/verify",
68
+ ];
69
+ // ---------------------------------------------------------------------------
70
+ // FNV-1a (32-bit) — same as used elsewhere in criptor for consistency
71
+ // ---------------------------------------------------------------------------
72
+ function fnv1a32(n) {
73
+ let h = 0x811c9dc5 >>> 0;
74
+ // Hash the 4 bytes of a 32-bit int
75
+ for (let i = 0; i < 4; i++) {
76
+ h = Math.imul(h ^ ((n >>> (i * 8)) & 0xff), 0x01000193) >>> 0;
77
+ }
78
+ return h >>> 0;
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Per-build key setup
82
+ //
83
+ // We want: key = fnv1a(epoch_day ^ PRODUCT_CONST) where PRODUCT_CONST is
84
+ // a per-build random constant embedded in the source.
85
+ //
86
+ // At build time we:
87
+ // 1. Pick PRODUCT_CONST (random, per-build)
88
+ // 2. Compute today's epoch_day = Math.floor(Date.now() / 86400000)
89
+ // 3. Compute correctKey = fnv1a(epoch_day ^ PRODUCT_CONST)
90
+ // 4. Encode each fake string with a stream derived from correctKey
91
+ // 5. Emit the decoder with PRODUCT_CONST baked in
92
+ //
93
+ // At runtime (real env, today's date):
94
+ // epoch_day matches build-time → correct key → strings decode correctly
95
+ //
96
+ // At runtime (frozen sandbox):
97
+ // Date.now() returns frozen value (e.g. 0 or 1970) → wrong epoch_day
98
+ // → wrong key → XOR-decodes to garbage
99
+ // ---------------------------------------------------------------------------
100
+ function streamByte(key, idx) {
101
+ // Simple key-indexed stream: same FNV-1a used in the runtime decoder below.
102
+ let h = (key ^ Math.imul(idx + 1, 0x9e3779b1)) >>> 0;
103
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b) >>> 0;
104
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35) >>> 0;
105
+ return ((h ^ (h >>> 16)) >>> 0) & 0xff;
106
+ }
107
+ function encodeStr(s, key) {
108
+ const raw = Buffer.from(s, "utf8");
109
+ const out = Buffer.alloc(raw.length);
110
+ for (let i = 0; i < raw.length; i++) {
111
+ out[i] = raw[i] ^ streamByte(key, i);
112
+ }
113
+ return out.toString("base64");
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Name helpers
117
+ // ---------------------------------------------------------------------------
118
+ function rname(rng) {
119
+ return `_0x${((rng.int32() >>> 0) & 0xffff).toString(16).padStart(4, "0")}`;
120
+ }
121
+ function rconst4(rng) {
122
+ return `0x${((rng.int32() >>> 0)).toString(16)}`;
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Main export
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Build the temporal-key decoder source block.
129
+ *
130
+ * Returns a JS string containing:
131
+ * - Per-build PRODUCT_CONST embedded as a hex literal
132
+ * - Fake string table encoded with today's epoch-day-derived key
133
+ * - Decoder function that re-derives the key at runtime
134
+ * - Self-test call (result discarded with void)
135
+ *
136
+ * Injected AFTER cneObfuscate() — names are already in _0x... format.
137
+ */
138
+ function buildTemporalKeySource(rng) {
139
+ // ── Build-time key derivation ─────────────────────────────────────────────
140
+ const productConst = (rng.int32() >>> 0);
141
+ const epochDay = Math.floor(Date.now() / 86400000); // today's UTC day
142
+ const correctKey = fnv1a32(epochDay ^ productConst);
143
+ // Pick a random subset of FAKE_STRINGS (6–10 entries)
144
+ const count = 6 + Math.floor(rng.next() * 5); // 6..10
145
+ const pool = [...FAKE_STRINGS].sort(() => rng.next() - 0.5);
146
+ const chosen = pool.slice(0, Math.min(count, pool.length));
147
+ // Encode each fake string with the correct key
148
+ const encodedEntries = chosen.map(s => encodeStr(s, correctKey));
149
+ // ── Per-build random names ─────────────────────────────────────────────────
150
+ const caName = rname(rng); // string array
151
+ const decName = rname(rng); // decoder function
152
+ const keyName = rname(rng); // derived key var
153
+ const dayName = rname(rng); // epoch day var
154
+ const iName = rname(rng); // loop var
155
+ const rawName = rname(rng); // raw buffer var
156
+ const sName = rname(rng); // stream accumulator
157
+ const hName = rname(rng); // hash temp var
158
+ const outName = rname(rng); // output string var
159
+ const idxParam = rname(rng); // decoder param
160
+ // FNV-1a stream byte sub-expression for the runtime decoder
161
+ // h = ((key ^ imul(i+1, 0x9e3779b1)) >>> 0); then two rounds; & 0xff
162
+ // We inline it as a single expression in the loop for compactness.
163
+ const fnvInline = (keyV, idxV) => `(function(${hName}){` +
164
+ `${hName}=Math.imul(${hName}^(${hName}>>>15),0x85ebca6b)>>>0;` +
165
+ `${hName}=Math.imul(${hName}^(${hName}>>>13),0xc2b2ae35)>>>0;` +
166
+ `return(${hName}^(${hName}>>>16))&0xff;` +
167
+ `})((${keyV}^Math.imul(${idxV}+1,0x9e3779b1))>>>0)`;
168
+ // FNV-1a of an integer (key derivation)
169
+ const fnvOfInt = (v) => `(function(_h,_i){` +
170
+ `for(_i=0;_i<4;_i++){_h=Math.imul(_h^((${v}>>>(_i*8))&0xff),0x01000193)>>>0;}` +
171
+ `return _h>>>0;` +
172
+ `})(0x811c9dc5>>>0,0)`;
173
+ const productConstHex = `0x${productConst.toString(16)}`;
174
+ return [
175
+ "/* layer-3h */",
176
+ // String array — encoded with today's epoch-day key
177
+ `var ${caName} = ${JSON.stringify(encodedEntries)};`,
178
+ // Decoder function
179
+ `function ${decName}(${idxParam}) {`,
180
+ ` if (!${caName}[${idxParam}]) return "";`,
181
+ ` var ${dayName} = Math.floor(Date.now() / 86400000);`,
182
+ ` var ${keyName} = ${fnvOfInt(`${dayName} ^ ${productConstHex}`)};`,
183
+ ` var ${rawName} = Buffer.from(${caName}[${idxParam}], "base64");`,
184
+ ` var ${outName} = "";`,
185
+ ` for (var ${iName} = 0; ${iName} < ${rawName}.length; ${iName}++) {`,
186
+ ` ${outName} += String.fromCharCode(${rawName}[${iName}] ^ ${fnvInline(keyName, iName)});`,
187
+ ` }`,
188
+ ` return ${outName};`,
189
+ `}`,
190
+ // Self-test: call at module load, discard result.
191
+ // Real env → correct string; frozen sandbox → garbage.
192
+ `void ${decName}(0);`,
193
+ ].join("\n");
194
+ }
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // Layer 9 — Timing Oracle / Execution-Time Semantic Drift
4
+ //
5
+ // CONCEPT:
6
+ // In a real Node.js process, Date.now() calls made in a tight loop will
7
+ // show non-zero jitter (OS scheduler, GC, I/O interrupts).
8
+ // In vm.runInContext / headless emulators / static instrumentation, the
9
+ // clock is often frozen, mocked, or deterministic — stddev ≈ 0.
10
+ //
11
+ // Unlike a crash-based anti-debug (which announces "this is protected"),
12
+ // this layer SILENTLY returns plausible but wrong results:
13
+ // - A forged enterprise key validates as { tier:"basic", features:1 }
14
+ // - The attacker's test says "key is valid" and moves on
15
+ // - But the extracted algorithm produces wrong output in production
16
+ //
17
+ // This is maximally evil: the attacker is CONFIDENT they succeeded.
18
+ //
19
+ // DETECTION:
20
+ // We sample Date.now() 8 times in a tight sync loop and measure variance.
21
+ // Real Node.js: at least 1 sample differs from t0 (variance > 0)
22
+ // Mocked/emulator: all samples === t0 (variance = 0)
23
+ // Fast machines: we also check process.hrtime.bigint() drift (ns precision)
24
+ //
25
+ // DRIFT INJECTION:
26
+ // A runtime IIFE is injected that:
27
+ // 1. Measures clock variance at startup (once, cached in __toc)
28
+ // 2. Exports a tiny __drift() helper that returns 0 in production,
29
+ // a non-zero scramble key in emulation.
30
+ // 3. Protected functions XOR their return value tier bits with __drift().
31
+ // In production: __drift() = 0, XOR is no-op, correct result.
32
+ // In emulation: __drift() ≠ 0, tier bits scrambled, wrong result.
33
+ //
34
+ // IMPLEMENTATION NOTE:
35
+ // The drift injection wraps the user's original function bodies.
36
+ // We don't modify logic — we XOR one integer field in the return object.
37
+ // Specifically: the `features` field (bitmask) is XOR'd with __drift().
38
+ // Basic tier (features=1) XOR'd with e.g. 0x3E → 0x3F → looks like
39
+ // enterprise features — but tier name is still "basic". Inconsistent.
40
+ // The attacker sees garbage and thinks their algo is wrong, not the protection.
41
+ // ============================================================================
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.buildDriftDetectorSource = buildDriftDetectorSource;
44
+ exports.applyTimingOracle = applyTimingOracle;
45
+ /**
46
+ * Returns the JS source of the __drift() detector IIFE.
47
+ * driftKey: the per-build property name under which the drift fn is exported
48
+ * (stored on globalThis and module.exports so user code can find it by that key).
49
+ * Also declares a local `var __drift` alias so pre-CNE AST references still resolve.
50
+ */
51
+ function buildDriftDetectorSource(rng, driftKey) {
52
+ // Use a random scramble key per build — different every protection
53
+ const scrambleKey = (rng.int32() & 0x1E) | 1; // non-zero, low 5 bits set
54
+ const samples = 8 + (rng.int32() & 3); // 8–11 samples
55
+ // Per-build random names for all identifiers — no fixed __drift / __toc fingerprint
56
+ const tocName = `_0x${((rng.int32() >>> 0) & 0xffffff).toString(16).padStart(6, '0')}`;
57
+ const driftName = `_0x${((rng.int32() >>> 0) & 0xffffff).toString(16).padStart(6, '0')}`;
58
+ const t0Name = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
59
+ const diffName = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
60
+ const iName = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
61
+ const tName = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
62
+ const hrtName = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
63
+ const h0Name = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
64
+ const jName = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
65
+ const h1Name = `_${((rng.int32() >>> 0) & 0xffff).toString(36)}`;
66
+ // Use provided driftKey or fall back to '__drift' (legacy/jsobf path).
67
+ // All internal vars use per-build random names; only the export key is fixed.
68
+ const exportKey = driftKey || '__drift';
69
+ return `;(function(){
70
+ var ${tocName}=(function(){
71
+ var ${t0Name}=Date.now(),${diffName}=0;
72
+ for(var ${iName}=0;${iName}<${samples};${iName}++){
73
+ var ${tName}=Date.now();
74
+ if(${tName}!==${t0Name}){${diffName}=${tName}-${t0Name};break;}
75
+ }
76
+ var ${hrtName}=0;
77
+ if(typeof process!=='undefined'&&process.hrtime){
78
+ var ${h0Name}=process.hrtime();
79
+ for(var ${jName}=0;${jName}<${samples};${jName}++){
80
+ var ${h1Name}=process.hrtime(${h0Name});
81
+ if(${h1Name}[0]>0||${h1Name}[1]>500){${hrtName}=1;break;}
82
+ }
83
+ }
84
+ return(${diffName}>0||${hrtName}>0)?0:${scrambleKey};
85
+ })();
86
+ if(typeof global!=='undefined'){global['${exportKey}']=function(){return ${tocName};};}
87
+ if(typeof module!=='undefined'&&module.exports){module.exports['${exportKey}']=function(){return ${tocName};};}
88
+ var ${exportKey}=function(){return ${tocName};};
89
+ })();`;
90
+ }
91
+ /**
92
+ * Wraps return-object expressions in functions that look like validators.
93
+ * Specifically: if a function returns an object with a `features` key,
94
+ * we XOR features with __drift() so the result is wrong in emulation.
95
+ *
96
+ * Operates post-Babel, pre-jsobf (on the generated code string).
97
+ * Uses a simple regex + string replacement — no AST needed at this stage.
98
+ */
99
+ function applyTimingOracle(ast, traverse, t, rng, driftName = "__drift") {
100
+ // Walk all ReturnStatement nodes. If they return an ObjectExpression
101
+ // that has a 'features' property, wrap features value with __drift() XOR.
102
+ traverse(ast, {
103
+ ReturnStatement(path) {
104
+ if (path.node.__obf)
105
+ return;
106
+ const arg = path.node.argument;
107
+ if (!arg || arg.type !== "ObjectExpression")
108
+ return;
109
+ // Find 'features' property
110
+ for (const prop of arg.properties) {
111
+ if (prop.type !== "ObjectProperty" ||
112
+ prop.computed ||
113
+ (prop.key.type !== "Identifier" || prop.key.name !== "features") &&
114
+ (prop.key.type !== "StringLiteral" || prop.key.value !== "features"))
115
+ continue;
116
+ // Wrap: features: X => features: (X) ^ (typeof __drift==='function'?__drift():0)
117
+ const original = prop.value;
118
+ if (original.__obf)
119
+ continue;
120
+ // Build: original ^ (typeof driftName === 'function' ? driftName() : 0)
121
+ const driftCall = t.conditionalExpression(t.binaryExpression("===", t.unaryExpression("typeof", t.identifier(driftName)), t.stringLiteral("function")), t.callExpression(t.identifier(driftName), []), t.numericLiteral(0));
122
+ const xored = t.binaryExpression("^", original, driftCall);
123
+ prop.value = xored;
124
+ // Don't mark __obf so MBA can still process the original value
125
+ break;
126
+ }
127
+ },
128
+ });
129
+ }
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeOpTable = makeOpTable;
4
+ exports.applyVm = applyVm;
5
+ // ============================================================================
6
+ // transform-vm.ts — Stage D: KrakVM virtualization (replaces stack-based VM)
7
+ //
8
+ // Based on KrakVM by krakes-dev (MIT licence):
9
+ // https://github.com/krakes-dev/KrakVm
10
+ //
11
+ // Replaces each FunctionDeclaration with a runVM(base64) call whose runtime
12
+ // is the per-build polymorphic KrakVM interpreter (krak-vm-core.ts).
13
+ //
14
+ // KrakVM architecture:
15
+ // - 256-register, stack-assisted RISC bytecode
16
+ // - Per-build opcode byte randomisation (Fisher-Yates on 256 slots)
17
+ // - Per-build arg-fetch order randomisation per handler
18
+ // - Per-build ctx property renaming to hex strings
19
+ // - Per-build LCG parameters for bytecode encryption
20
+ // - ~40% of handlers are "dynamic" (injected mid-execution via eval)
21
+ // - Per-build VmNameMap: all internal identifiers randomised (readByte,
22
+ // runVM, ops, ctx, LCG_MUL, etc.) — no fixed names in output
23
+ //
24
+ // Contract with pipeline.ts:
25
+ // - applyVm() is called before MBA/opaque/string-cipher passes
26
+ // - Returns count of virtualised functions (0 = no runtime injected)
27
+ // - All generated AST nodes are marked __obf + __cne to skip later passes
28
+ // ============================================================================
29
+ const krak_vm_core_1 = require("./krak-vm-core");
30
+ const krak_compiler_1 = require("./krak-compiler");
31
+ // Re-export makeOpTable as no-op for pipeline.ts compat
32
+ function makeOpTable(_rng) { return {}; }
33
+ function hasPlainTag(node) {
34
+ return !!(node &&
35
+ node.leadingComments &&
36
+ node.leadingComments.some((c) => /@plain\b/.test(c.value)));
37
+ }
38
+ function markAllObf(node) {
39
+ if (!node || typeof node !== "object")
40
+ return;
41
+ node.__obf = true;
42
+ node.__cne = true;
43
+ for (const k of Object.keys(node)) {
44
+ const v = node[k];
45
+ if (Array.isArray(v))
46
+ v.forEach(markAllObf);
47
+ else if (v && typeof v === "object" && v.type)
48
+ markAllObf(v);
49
+ }
50
+ }
51
+ function buildWrapper(t, origNode, base64, runVmName, refsToExpose, // top-level names (fns + vars) to plant on globalThis for GGLO
52
+ kvArgs, // per-build globalThis key for argument array
53
+ kvRet) {
54
+ const paramNames = origNode.params.map((p) => p.name);
55
+ const stmts = [];
56
+ // globalThis["<name>"] = <name> — expose module-scope names for GGLO.
57
+ // MUST use computed bracket+StringLiteral so CNE renames the value identifier
58
+ // but NOT the string key — the VM bytecode has the original string baked in.
59
+ for (const ref of refsToExpose) {
60
+ const keyLit = t.stringLiteral(ref);
61
+ keyLit.__obf = true; // skip string-cipher on infra keys
62
+ stmts.push(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.identifier("globalThis"), keyLit, /* computed= */ true), t.identifier(ref))));
63
+ }
64
+ // globalThis[kv_args] = [a, b, c, ...]
65
+ const argsArray = t.arrayExpression(paramNames.map((n) => t.identifier(n)));
66
+ stmts.push(t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.identifier("globalThis"), t.stringLiteral(kvArgs), /* computed= */ true), argsArray)));
67
+ // runVM("...base64...")
68
+ const b64Lit = t.stringLiteral(base64);
69
+ b64Lit.__obf = true;
70
+ stmts.push(t.expressionStatement(t.callExpression(t.identifier(runVmName), [b64Lit])));
71
+ // return globalThis[kv_ret]
72
+ stmts.push(t.returnStatement(t.memberExpression(t.identifier("globalThis"), t.stringLiteral(kvRet), /* computed= */ true)));
73
+ const wrapper = t.functionDeclaration(t.identifier(origNode.id.name), origNode.params.map((p) => t.identifier(p.name)), t.blockStatement(stmts));
74
+ wrapper.__cne = true;
75
+ wrapper.__obf = true;
76
+ return wrapper;
77
+ }
78
+ function buildSyntheticSource(fnNode, generate, kvG, // local var name for globalThis ref
79
+ kvArgs, // globalThis key for args array
80
+ kvRet, // globalThis key for return value
81
+ kvFn) {
82
+ const bodyCode = generate(fnNode.body, { compact: false, comments: false }).code;
83
+ const params = fnNode.params.map((p) => p.name);
84
+ const paramDecls = params.map((name, i) => `var ${name} = ${kvG}["${kvArgs}"][${i}];`).join("\n");
85
+ return (`var ${kvG} = (typeof globalThis !== 'undefined') ? globalThis : (typeof global !== 'undefined' ? global : {});\n` +
86
+ `${paramDecls}\n` +
87
+ `function ${kvFn}(${params.join(", ")}) ${bodyCode}\n` +
88
+ `${kvG}["${kvRet}"] = ${kvFn}(${params.join(", ")});`);
89
+ }
90
+ // RUN_VM_NAME is now per-build from nameMap.kv_run — see applyVm()
91
+ function applyVm(ast, traverse, parse, t, rng, _Hbuild, _opTable) {
92
+ const { runtimeSrc, config, nameMap } = (0, krak_vm_core_1.generateVm)(rng);
93
+ // Per-build names for all cross-boundary identifiers
94
+ const RUN_VM_NAME = nameMap.kv_run;
95
+ const KV_ARGS = nameMap.kv_args;
96
+ const KV_RET = nameMap.kv_ret;
97
+ const KV_G = nameMap.kv_g;
98
+ const KV_FN = nameMap.kv_fn;
99
+ let count = 0;
100
+ let skipped = 0;
101
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
102
+ const _gen = require("@babel/generator");
103
+ const generate = _gen.default ?? _gen;
104
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
105
+ const _parser = require("@babel/parser");
106
+ const parse2 = _parser.parse ?? _parser;
107
+ // ── Pass 0: collect top-level bindings ─────────────────────────────────────
108
+ // All top-level names (FunctionDeclarations + VariableDeclarations + imported
109
+ // bindings) that the compiler might look up via GGLO. We expose them on
110
+ // globalThis in the wrapper so the VM's GGLO opcode (ctx.env[name]) can resolve
111
+ // them. Imports MUST be included: a virtualized function that references an
112
+ // imported binding (e.g. `createHash` from "node:crypto") otherwise compiles a
113
+ // GGLO with no matching globalThis entry → the name resolves to undefined at
114
+ // runtime and the function silently returns garbage.
115
+ const topLevelFnNames = new Set();
116
+ const topLevelVarNames = new Set();
117
+ const collectDecl = (decl) => {
118
+ if (!decl)
119
+ return;
120
+ if (decl.type === "FunctionDeclaration" && decl.id) {
121
+ topLevelFnNames.add(decl.id.name);
122
+ }
123
+ else if (decl.type === "VariableDeclaration") {
124
+ for (const d of decl.declarations) {
125
+ if (d.id?.type === "Identifier")
126
+ topLevelVarNames.add(d.id.name);
127
+ }
128
+ }
129
+ else if (decl.type === "ClassDeclaration" && decl.id) {
130
+ topLevelVarNames.add(decl.id.name);
131
+ }
132
+ };
133
+ for (const node of ast.program.body) {
134
+ if (node.type === "ImportDeclaration") {
135
+ // import { a, b as c } from "m" / import d from "m" / import * as ns from "m"
136
+ for (const spec of node.specifiers) {
137
+ if (spec.local?.type === "Identifier")
138
+ topLevelVarNames.add(spec.local.name);
139
+ }
140
+ }
141
+ else if (node.type === "ExportNamedDeclaration" ||
142
+ node.type === "ExportDefaultDeclaration") {
143
+ // `export function f(){}` / `export const x=…` keep their declaration in
144
+ // `node.declaration` — unwrap it so the inner binding name is collected.
145
+ collectDecl(node.declaration);
146
+ }
147
+ else {
148
+ collectDecl(node);
149
+ }
150
+ }
151
+ // calledLocally : fn names called from inside other fns — must stay plain JS.
152
+ // localRefs : for each fn, ALL top-level identifiers it references
153
+ // (called fns + module vars). These get planted on globalThis
154
+ // before the VM wrapper runs so GGLO can resolve them.
155
+ const calledLocally = new Set();
156
+ const localRefs = new Map();
157
+ const allTopLevel = new Set([...topLevelFnNames, ...topLevelVarNames]);
158
+ traverse(ast, {
159
+ FunctionDeclaration(path) {
160
+ const fnName = path.node.id?.name;
161
+ if (!topLevelFnNames.has(fnName))
162
+ return;
163
+ const refs = new Set();
164
+ localRefs.set(fnName, refs);
165
+ path.traverse({
166
+ Identifier(inner) {
167
+ const name = inner.node.name;
168
+ if (allTopLevel.has(name) && name !== fnName)
169
+ refs.add(name);
170
+ },
171
+ CallExpression(inner) {
172
+ const callee = inner.node.callee;
173
+ if (callee.type === "Identifier" && topLevelFnNames.has(callee.name)) {
174
+ calledLocally.add(callee.name); // callee must stay plain JS
175
+ }
176
+ },
177
+ });
178
+ },
179
+ });
180
+ traverse(ast, {
181
+ FunctionDeclaration(path) {
182
+ const node = path.node;
183
+ if (node.__obf)
184
+ return;
185
+ if (hasPlainTag(node) || hasPlainTag(path.parent))
186
+ return;
187
+ if (!node.id || !node.body || node.body.type !== "BlockStatement")
188
+ return;
189
+ // Skip callees — they must stay real JS functions so callers can
190
+ // reference them by name at the module level.
191
+ if (calledLocally.has(node.id.name))
192
+ return;
193
+ // Bug fix 1 (async/await): async functions contain `await` expressions that
194
+ // are only legal inside an async context. buildSyntheticSource wraps the body
195
+ // in a plain sync function, so the bytecode parse step throws
196
+ // "SyntaxError: Unexpected reserved word 'await'". Skip async functions.
197
+ if (node.async) {
198
+ skipped++;
199
+ return;
200
+ }
201
+ // Bug fix 2 (property name undefined): params that are not plain Identifiers
202
+ // (destructured patterns, default values, rest elements) don't have a .name
203
+ // property, causing `p.name` to return undefined in buildSyntheticSource and
204
+ // buildWrapper, which later surfaces as "Property name expected string but got
205
+ // undefined". Skip any function with non-identifier params.
206
+ const allParamsSimple = node.params.every((p) => p.type === "Identifier");
207
+ if (!allParamsSimple) {
208
+ skipped++;
209
+ return;
210
+ }
211
+ let synthSrc;
212
+ try {
213
+ synthSrc = buildSyntheticSource(node, generate, KV_G, KV_ARGS, KV_RET, KV_FN);
214
+ }
215
+ catch {
216
+ skipped++;
217
+ return;
218
+ }
219
+ let compiled;
220
+ try {
221
+ const compiler = new krak_compiler_1.KrakCompiler(config);
222
+ // Bug fix 3 (RegExpLiteral + any other parse/compile error): the original
223
+ // code only caught KrakBailError and re-threw everything else, so any
224
+ // unexpected construct (regex literals, generators, template literals, etc.)
225
+ // would crash the whole worker. Catch ALL errors here and bail gracefully.
226
+ const synthAst = parse2(synthSrc, {
227
+ sourceType: "module",
228
+ allowReturnOutsideFunction: true,
229
+ });
230
+ compiled = compiler.compileProgram(synthAst.program.body);
231
+ }
232
+ catch (e) {
233
+ skipped++;
234
+ console.error(` [vm/krak] skipped ${node.id.name}: ${e.message}`);
235
+ return;
236
+ }
237
+ const refsToExpose = [...(localRefs.get(node.id.name) ?? [])];
238
+ const wrapper = buildWrapper(t, node, compiled.base64, RUN_VM_NAME, refsToExpose, KV_ARGS, KV_RET);
239
+ path.replaceWith(wrapper);
240
+ path.skip();
241
+ count++;
242
+ console.error(` [vm/krak] virtualised ${node.id.name}: ${compiled.bytecode.length}B bytecode`);
243
+ },
244
+ });
245
+ if (skipped > 0)
246
+ console.error(` [vm/krak] left ${skipped} fn(s) plain (unsupported syntax / @plain)`);
247
+ if (count > 0) {
248
+ const dynamicOpsCode = Object.values(config.dynamicOps)
249
+ .map((op) => op.src)
250
+ .join("\n");
251
+ // nameMap.runVM is the renamed internal runVM symbol; RUN_VM_NAME is the
252
+ // per-build outer alias planted on globalThis for the wrapper functions.
253
+ const runVmInternal = nameMap.runVM;
254
+ const fullVmSrc = `(function(){\n` +
255
+ runtimeSrc + "\n" +
256
+ dynamicOpsCode + "\n" +
257
+ `var ${RUN_VM_NAME} = ${runVmInternal};\n` +
258
+ `if(typeof globalThis!=='undefined')globalThis["${RUN_VM_NAME}"]=${RUN_VM_NAME};\n` +
259
+ `})();\n` +
260
+ `var ${RUN_VM_NAME} = (typeof globalThis!=='undefined' ? globalThis["${RUN_VM_NAME}"] : (typeof global!=='undefined' ? global["${RUN_VM_NAME}"] : undefined));`;
261
+ const vmAst = parse(fullVmSrc, { sourceType: "module", allowReturnOutsideFunction: true });
262
+ vmAst.program.body.forEach(markAllObf);
263
+ ast.program.body.unshift(...vmAst.program.body);
264
+ }
265
+ return count;
266
+ }