@mochi.js/inject
Advanced tools
| /** | ||
| * Unit: `mouse-event-screen` inject module (R-041 lock). | ||
| * | ||
| * Asserts the patched MouseEvent.prototype getters return | ||
| * `clientX + window.screenX` (and Y), and that the descriptor is shaped | ||
| * like Chrome's native `{ configurable: true, enumerable: true }` with a | ||
| * cloaked `[native code]` toString. | ||
| * | ||
| * Sandbox seeds `window.screenX = 50, window.screenY = 75` (see | ||
| * `sandbox.ts`). The fake `MouseEvent` constructor copies clientX/Y from | ||
| * the init dict onto the instance; the patched prototype getter reads | ||
| * those + window.screenX/Y to produce the screen-relative value. | ||
| * | ||
| * @see tasks/0250-mouseevent-screen-coords.md | ||
| * @see PLAN.md §5.3 | ||
| */ | ||
| import { describe, expect, it } from "bun:test"; | ||
| import { buildPayload } from "../build"; | ||
| import { FIXTURE_MATRIX } from "./fixtures"; | ||
| import { runPayloadInSandbox } from "./sandbox"; | ||
| describe("inject runtime overrides — MouseEvent.screenX/screenY (R-041)", () => { | ||
| it("patched screenX returns clientX + window.screenX", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| const sb = runPayloadInSandbox(code); | ||
| const Win = sb.window as Record<string, unknown>; | ||
| const wx = Win.screenX as number; | ||
| const ev = new sb.MouseEvent("test", { clientX: 100, clientY: 200 }); | ||
| expect((ev as { screenX: number }).screenX).toBe(100 + wx); | ||
| }); | ||
| it("patched screenY returns clientY + window.screenY", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| const sb = runPayloadInSandbox(code); | ||
| const Win = sb.window as Record<string, unknown>; | ||
| const wy = Win.screenY as number; | ||
| const ev = new sb.MouseEvent("test", { clientX: 100, clientY: 200 }); | ||
| expect((ev as { screenY: number }).screenY).toBe(200 + wy); | ||
| }); | ||
| it("works with zero coords (clientX=0 → screenX === window.screenX)", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| const sb = runPayloadInSandbox(code); | ||
| const Win = sb.window as Record<string, unknown>; | ||
| const ev = new sb.MouseEvent("test", { clientX: 0, clientY: 0 }); | ||
| expect((ev as { screenX: number }).screenX).toBe(Win.screenX as number); | ||
| expect((ev as { screenY: number }).screenY).toBe(Win.screenY as number); | ||
| }); | ||
| it("descriptor mirrors Chrome's native shape (configurable:true, enumerable:true)", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| const sb = runPayloadInSandbox(code); | ||
| const proto = sb.MouseEvent.prototype as Record<string, unknown>; | ||
| const dx = Object.getOwnPropertyDescriptor(proto, "screenX"); | ||
| const dy = Object.getOwnPropertyDescriptor(proto, "screenY"); | ||
| expect(dx).toBeDefined(); | ||
| expect(dy).toBeDefined(); | ||
| expect(dx?.configurable).toBe(true); | ||
| expect(dx?.enumerable).toBe(true); | ||
| expect(dy?.configurable).toBe(true); | ||
| expect(dy?.enumerable).toBe(true); | ||
| expect(typeof dx?.get).toBe("function"); | ||
| expect(typeof dy?.get).toBe("function"); | ||
| }); | ||
| it("getter.toString() is cloaked to native shape", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| const sb = runPayloadInSandbox(code); | ||
| const proto = sb.MouseEvent.prototype as Record<string, unknown>; | ||
| const dx = Object.getOwnPropertyDescriptor(proto, "screenX"); | ||
| const dy = Object.getOwnPropertyDescriptor(proto, "screenY"); | ||
| // The toString cloak runs in the sandbox's `Function.prototype.toString`, | ||
| // so we must call into the sandbox to stringify the getter. | ||
| const sbFnToString = (sb.Function.prototype as { toString: (this: unknown) => string }) | ||
| .toString; | ||
| const sxStr = sbFnToString.call(dx?.get as unknown); | ||
| const syStr = sbFnToString.call(dy?.get as unknown); | ||
| expect(sxStr).toBe("function get screenX() { [native code] }"); | ||
| expect(syStr).toBe("function get screenY() { [native code] }"); | ||
| }); | ||
| it("payload code includes the mouse-event-screen module marker", () => { | ||
| const { code } = buildPayload(FIXTURE_MATRIX); | ||
| expect(code).toContain("mochi:mouse-event-screen"); | ||
| expect(code).toContain("MouseEvent.prototype"); | ||
| }); | ||
| }); |
| /** | ||
| * Spoof module: `MouseEvent.prototype.screenX` / `screenY`. | ||
| * | ||
| * Closes a relational-consistency leak (PLAN.md I-5) on CDP-dispatched | ||
| * mouse events. When `Input.dispatchMouseEvent` synthesizes a click, the | ||
| * `screenX`/`screenY` slots come from the dispatch params and DO NOT | ||
| * include the browser window's screen offset — sites reading | ||
| * `event.screenX` see e.g. `0` or a viewport-relative value rather than | ||
| * the screen-relative coord a real OS-level mouse event would carry. | ||
| * | ||
| * Real-user mouse events satisfy: | ||
| * `event.screenX === event.clientX + window.screenX` | ||
| * `event.screenY === event.clientY + window.screenY` | ||
| * | ||
| * We patch the prototype getters to return that derived value so dispatched | ||
| * events match what real input devices emit. The replacement getters are | ||
| * registered with the toString cloak so `Object.getOwnPropertyDescriptor( | ||
| * MouseEvent.prototype, "screenX").get.toString()` returns the native shape | ||
| * `function get screenX() { [native code] }`. | ||
| * | ||
| * Source-cited reference: puppeteer-real-browser | ||
| * `lib/cjs/module/pageController.js:48-58`. Origin: | ||
| * `TheFalloutOf76/CDP-bug-MouseEvent-.screenX-.screenY-patcher`. | ||
| * | ||
| * No matrix inputs — the patch is a relational identity, not a value spoof. | ||
| * | ||
| * @see tasks/0250-mouseevent-screen-coords.md | ||
| * @see PLAN.md §5.3, §8.4 | ||
| */ | ||
| export function emitMouseEventScreenModule(): string { | ||
| return ` | ||
| // ---- MouseEvent.screenX / screenY prototype patch ------------------------- | ||
| (function() { | ||
| if (typeof MouseEvent === "undefined" || !MouseEvent.prototype) return; | ||
| var proto = MouseEvent.prototype; | ||
| // Capture original descriptors so we can mirror enumerable/configurable. | ||
| // Chrome's native: { configurable: true, enumerable: true, get: native }. | ||
| var dx = __mochi_getOwnPropertyDescriptor__(proto, "screenX"); | ||
| var dy = __mochi_getOwnPropertyDescriptor__(proto, "screenY"); | ||
| var configurableX = dx !== undefined ? !!dx.configurable : true; | ||
| var enumerableX = dx !== undefined ? !!dx.enumerable : true; | ||
| var configurableY = dy !== undefined ? !!dy.configurable : true; | ||
| var enumerableY = dy !== undefined ? !!dy.enumerable : true; | ||
| function screenX() { | ||
| var cx = this !== undefined && this !== null ? this.clientX : 0; | ||
| var wx = typeof window !== "undefined" && typeof window.screenX === "number" | ||
| ? window.screenX : 0; | ||
| return cx + wx; | ||
| } | ||
| function screenY() { | ||
| var cy = this !== undefined && this !== null ? this.clientY : 0; | ||
| var wy = typeof window !== "undefined" && typeof window.screenY === "number" | ||
| ? window.screenY : 0; | ||
| return cy + wy; | ||
| } | ||
| __mochi_register_native__(screenX, "get screenX"); | ||
| __mochi_register_native__(screenY, "get screenY"); | ||
| try { | ||
| __mochi_defineProperty__(proto, "screenX", { | ||
| configurable: configurableX, enumerable: enumerableX, get: screenX, | ||
| }); | ||
| } catch (_e) {} | ||
| try { | ||
| __mochi_defineProperty__(proto, "screenY", { | ||
| configurable: configurableY, enumerable: enumerableY, get: screenY, | ||
| }); | ||
| } catch (_e) {} | ||
| })(); | ||
| `; | ||
| } |
+2
-2
| { | ||
| "name": "@mochi.js/inject", | ||
| "version": "0.1.1", | ||
| "version": "0.2.0", | ||
| "description": "Zero-jitter stealth payload for mochi — JIT-friendly proxies installed before any page script.", | ||
@@ -42,3 +42,3 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@mochi.js/consistency": "^0.1.0" | ||
| "@mochi.js/consistency": "^0.1.1" | ||
| }, | ||
@@ -45,0 +45,0 @@ "publishConfig": { |
@@ -81,2 +81,7 @@ /** | ||
| }), | ||
| "mouseEvent-screen-formula": JSON.stringify({ | ||
| screenX: "clientX + window.screenX", | ||
| screenY: "clientY + window.screenY", | ||
| rule: "R-041", | ||
| }), | ||
| }, | ||
@@ -83,0 +88,0 @@ entropyBudget: { fixed: [], perSeed: [] }, |
@@ -31,2 +31,12 @@ /** | ||
| WebGL2RenderingContext: { prototype: Record<string, unknown> }; | ||
| // Minimal MouseEvent fake — constructor that copies clientX/clientY from | ||
| // init dict onto `this` and walks the prototype for screenX/screenY. The | ||
| // mouse-event-screen module installs its prototype getters here. | ||
| MouseEvent: { | ||
| new ( | ||
| type: string, | ||
| init?: { clientX?: number; clientY?: number; screenX?: number; screenY?: number }, | ||
| ): Record<string, unknown>; | ||
| prototype: Record<string, unknown>; | ||
| }; | ||
| Intl: typeof Intl; | ||
@@ -194,2 +204,40 @@ FontFace: typeof FontFace | undefined; | ||
| // MouseEvent stub. Real Chrome's MouseEvent.prototype.screenX is a native | ||
| // accessor (configurable:true, enumerable:true). Mirror that shape so the | ||
| // patched module's defineProperty doesn't trip on a non-configurable slot. | ||
| type MEProto = Record<string, unknown>; | ||
| const meProto: MEProto = Object.create(Object.prototype); | ||
| Object.defineProperty(meProto, "screenX", { | ||
| configurable: true, | ||
| enumerable: true, | ||
| get() { | ||
| return 0; | ||
| }, | ||
| }); | ||
| Object.defineProperty(meProto, "screenY", { | ||
| configurable: true, | ||
| enumerable: true, | ||
| get() { | ||
| return 0; | ||
| }, | ||
| }); | ||
| const MouseEvent = function MouseEvent( | ||
| this: Record<string, unknown>, | ||
| _type: string, | ||
| init?: { clientX?: number; clientY?: number; screenX?: number; screenY?: number }, | ||
| ) { | ||
| this.clientX = init?.clientX ?? 0; | ||
| this.clientY = init?.clientY ?? 0; | ||
| // Note: native screenX/Y come from the prototype getter, not instance | ||
| // slots — but unpatched MouseEvent in our sandbox lets us construct | ||
| // with a screenX init for completeness. | ||
| if (init !== undefined && init.screenX !== undefined) { | ||
| this.__nativeScreenX = init.screenX; | ||
| } | ||
| if (init !== undefined && init.screenY !== undefined) { | ||
| this.__nativeScreenY = init.screenY; | ||
| } | ||
| } as unknown as SandboxGlobals["MouseEvent"]; | ||
| MouseEvent.prototype = meProto; | ||
| // FontFaceSet stub. | ||
@@ -213,2 +261,3 @@ const fonts: Record<string, unknown> = { | ||
| WebGL2RenderingContext, | ||
| MouseEvent, | ||
| Intl, | ||
@@ -229,5 +278,8 @@ FontFace: typeof FontFace !== "undefined" ? FontFace : undefined, | ||
| // window === globalThis === sandbox itself | ||
| // window === globalThis === sandbox itself. The mouse-event-screen module | ||
| // reads `window.screenX/Y` — pin defaults so the formula is exercised. | ||
| (sandbox as unknown as { window: unknown }).window = sandbox; | ||
| (sandbox as unknown as { globalThis: unknown }).globalThis = sandbox; | ||
| (sandbox as unknown as { screenX: number }).screenX = 50; | ||
| (sandbox as unknown as { screenY: number }).screenY = 75; | ||
| return sandbox; | ||
@@ -256,2 +308,3 @@ } | ||
| "WebGL2RenderingContext", | ||
| "MouseEvent", | ||
| "Intl", | ||
@@ -282,2 +335,3 @@ "FontFace", | ||
| sandbox.WebGL2RenderingContext, | ||
| sandbox.MouseEvent, | ||
| sandbox.Intl, | ||
@@ -284,0 +338,0 @@ sandbox.FontFace, |
+5
-0
@@ -33,2 +33,3 @@ /** | ||
| import { emitMediaDevicesModule } from "./modules/media-devices"; | ||
| import { emitMouseEventScreenModule } from "./modules/mouse-event-screen"; | ||
| import { emitNavigatorModule } from "./modules/navigator"; | ||
@@ -119,2 +120,6 @@ import { emitNetworkInfoModule } from "./modules/network-info"; | ||
| parts.push(wrapTry("plugins", emitPluginsModule(matrix))); | ||
| // R-041: MouseEvent.screenX/screenY prototype patch — closes the I-5 | ||
| // relational leak on CDP-dispatched mouse events. See task 0250 + | ||
| // packages/consistency/src/rules/mouseEvent.ts. No matrix input. | ||
| parts.push(wrapTry("mouse-event-screen", emitMouseEventScreenModule())); | ||
@@ -121,0 +126,0 @@ // Self-deletion of any stray __mochi__* properties on window/globalThis |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
135774
7.65%29
7.41%3273
7%13
8.33%Updated