🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@mochi.js/inject

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mochi.js/inject - npm Package Compare versions

Comparing version
0.1.1
to
0.2.0
+88
src/__tests__/mouse-event-screen.test.ts
/**
* 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,

@@ -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