🚀 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.3.1
to
0.4.0
+102
src/__tests__/performance-timing.test.ts
/**
* Unit: performance-timing module — `PerformanceNavigationTiming` shim.
*
* Empirically discovered leak: Chrome launched via `--remote-debugging-pipe`
* (mochi's path) sometimes emits navigation entries with `dns: 0`, `tcp: 0`,
* `nextHopProtocol: ""` even on cold loads — a known headless tell that
* FPJS's tampering ML reads. The module wraps each navigation entry in a
* Proxy that injects realistic handshake durations only when the live
* values are zero.
*
* The sandbox in `sandbox.ts` doesn't stand up a real `performance` object
* with navigation entries, so these tests assert the SHAPE of the emitted
* JS — the same pattern `phase07-modules.test.ts` uses for webgpu / network
* info / etc. Runtime semantics are exercised by the harness E2E gate
* against real Chromium.
*
*/
import { describe, expect, it } from "bun:test";
import { emitPerformanceTimingModule } from "../modules/performance-timing";
import { FIXTURE_MATRIX } from "./fixtures";
describe("performance-timing — PerformanceNavigationTiming shim", () => {
it("emits a Proxy-wrapping override for getEntriesByType('navigation')", () => {
const code = emitPerformanceTimingModule(FIXTURE_MATRIX);
expect(code).toContain("performance-timing spoof");
expect(code).toContain("getEntriesByType");
expect(code).toContain("getEntries");
expect(code).toContain("getEntriesByName");
expect(code).toContain('entry.entryType !== "navigation"');
expect(code).toContain("new Proxy(entry,");
});
it("only patches the four leaky fields; other props pass through Reflect.get", () => {
const code = emitPerformanceTimingModule(FIXTURE_MATRIX);
expect(code).toContain('prop === "domainLookupEnd"');
expect(code).toContain('prop === "connectEnd"');
expect(code).toContain('prop === "secureConnectionStart"');
expect(code).toContain('prop === "nextHopProtocol"');
expect(code).toContain("Reflect.get(target, prop, receiver)");
});
it("uses idempotent patching — only injects when end <= start", () => {
const code = emitPerformanceTimingModule(FIXTURE_MATRIX);
// domainLookupEnd: only adds DNS_MS when end <= start (i.e. zero/coalesced)
expect(code).toMatch(/\(e <= s\) \? s \+ DNS_MS : e/);
// connectEnd similarly
expect(code).toMatch(/\(ce <= cs\) \? cs \+ TCP_MS \+ TLS_MS : ce/);
});
it("provides a toJSON override so JSON.stringify(entry) sees the patched values", () => {
const code = emitPerformanceTimingModule(FIXTURE_MATRIX);
expect(code).toContain('prop === "toJSON"');
expect(code).toContain("orig.domainLookupEnd");
expect(code).toContain("orig.nextHopProtocol");
});
it("derives TCP/TLS budgets from matrix.uaCh.connection.rtt when present", () => {
const matrix = {
...FIXTURE_MATRIX,
uaCh: {
...FIXTURE_MATRIX.uaCh,
connection: JSON.stringify({ rtt: 100, downlink: 10 }),
},
};
const code = emitPerformanceTimingModule(matrix);
// rtt=100 → tcp = round(100 * 0.55) = 55ms, tls = round(100 * 0.1) = 10ms
expect(code).toContain("var TCP_MS = 55;");
expect(code).toContain("var TLS_MS = 10;");
});
it("falls back to safe defaults when matrix.uaCh.connection is missing", () => {
const matrix = {
...FIXTURE_MATRIX,
uaCh: { ...FIXTURE_MATRIX.uaCh, connection: "" },
};
const code = emitPerformanceTimingModule(matrix);
// rtt absent → baseRtt = 50ms → tcp = 28ms, tls = 5ms (the values
// empirically observed on real Chrome on a real Aixit Frankfurt
// server, suspect score 8 — see investigation 2026-05-09).
expect(code).toContain("var TCP_MS = 28;");
expect(code).toContain("var TLS_MS = 5;");
});
it("clamps absurd RTT values so misconfigured matrices don't produce slow handshakes", () => {
const matrix = {
...FIXTURE_MATRIX,
uaCh: {
...FIXTURE_MATRIX.uaCh,
connection: JSON.stringify({ rtt: 5000 }),
},
};
const code = emitPerformanceTimingModule(matrix);
// rtt clamped to 200 → tcp = round(200 * 0.55) = 110ms
expect(code).toContain("var TCP_MS = 110;");
});
it("hardcodes nextHopProtocol fallback to h2", () => {
const code = emitPerformanceTimingModule(FIXTURE_MATRIX);
expect(code).toContain('var DEFAULT_PROTOCOL = "h2";');
});
});
/**
* Spoof module: `PerformanceNavigationTiming`.
*
* Reads from the matrix:
* - `matrix.uaCh.connection` (R-037) — `{rtt, downlink, ...}`. The `rtt`
* value seeds a plausible TCP handshake duration; absent → defaults.
*
* **What this fixes.** Chrome launched with `--remote-debugging-pipe` (the
* mochi launch path) and certain headless / virtualised network paths emit
* navigation entries with `dns: 0`, `tcp: 0`, `nextHopProtocol: ""` even on
* fresh cold loads — the connection-establishment phases are coalesced or
* never populated. That triad is a well-known headless tell. Real Chrome on
* a real cold load shows `dns ≈ 20-50ms`, `tcp ≈ 20-40ms`, and
* `nextHopProtocol = "h2"` (or "h3" / "http/1.1") for HTTPS/2 origins.
*
* **Strategy.** Wrap each `navigation` entry returned by
* `performance.getEntriesByType("navigation")` and `performance.getEntries()`
* in a `Proxy` that overrides only the fields known to leak (domainLookupEnd,
* connectEnd, secureConnectionStart, nextHopProtocol). Every other property
* (responseStart, responseEnd, transferSize, etc.) passes through unchanged
* so cache / load-time fields stay accurate. `instanceof
* PerformanceNavigationTiming` checks pass through the proxy transparently.
*
* Idempotence: only patches when the live entry has the leaky shape
* (start === end for the relevant phase). If Chrome populated real values
* (e.g. on a non-CDP launch path) the proxy returns them unchanged.
*
* Determinism: dns + tcp values derive from a constant seed (kept simple
* for v1 — no per-call PRNG since the entry is queried multiple times by
* the same probe and must return stable values).
*
* @see PLAN.md §9.6 (timing precision philosophy — same-engine v1 keeps
* Chrome's natural coarsening; this module only fixes the
* pipe-mode-specific zero-handshake leak, not timer precision).
*/
import type { MatrixV1 } from "@mochi.js/consistency";
interface Connection {
readonly rtt?: number;
}
function tryParse<T>(s: unknown): T | null {
if (typeof s !== "string" || s.length === 0) return null;
try {
return JSON.parse(s) as T;
} catch {
return null;
}
}
export function emitPerformanceTimingModule(matrix: MatrixV1): string {
const conn = tryParse<Connection>(matrix.uaCh.connection) ?? {};
// RTT seeds connect time; cap at 200ms so absurd values from misconfigured
// matrices don't produce comically slow handshakes (200ms TCP+TLS is still
// within real residential-broadband range).
const baseRtt = typeof conn.rtt === "number" && conn.rtt > 0 ? Math.min(conn.rtt, 200) : 50;
// DNS lookup is independent of RTT — pick a stable value in real-Chrome range.
const dnsMs = 30;
// TCP connect is roughly one RTT for a 3-way handshake on a warm cache.
const tcpMs = Math.max(20, Math.round(baseRtt * 0.55));
// TLS adds ~1 RTT after TCP for TLS 1.3 1-RTT handshake.
const tlsMs = Math.max(5, Math.round(baseRtt * 0.1));
return `
// ---- performance-timing spoof (PerformanceNavigationTiming) ----------------
(function() {
if (typeof performance === "undefined") return;
if (typeof performance.getEntriesByType !== "function") return;
var DNS_MS = ${dnsMs};
var TCP_MS = ${tcpMs};
var TLS_MS = ${tlsMs};
var DEFAULT_PROTOCOL = "h2";
function patchEntry(entry) {
if (entry === null || entry === undefined) return entry;
if (entry.entryType !== "navigation") return entry;
return new Proxy(entry, {
get: function(target, prop, receiver) {
if (prop === "domainLookupEnd") {
var s = target.domainLookupStart;
var e = target.domainLookupEnd;
return (e <= s) ? s + DNS_MS : e;
}
if (prop === "connectEnd") {
var cs = target.connectStart;
var ce = target.connectEnd;
return (ce <= cs) ? cs + TCP_MS + TLS_MS : ce;
}
if (prop === "secureConnectionStart") {
var v = target.secureConnectionStart;
if (v === 0 || v === undefined) {
return target.connectStart + TCP_MS;
}
return v;
}
if (prop === "nextHopProtocol") {
var p = target.nextHopProtocol;
return (p === "" || p === undefined) ? DEFAULT_PROTOCOL : p;
}
if (prop === "toJSON") {
// Page scripts that JSON-serialise the entry must see the same
// patched values rather than the raw zeroes.
return function() {
var orig = (typeof target.toJSON === "function") ? target.toJSON() : Object.assign({}, target);
var ds = target.domainLookupStart;
if (orig.domainLookupEnd <= ds) orig.domainLookupEnd = ds + DNS_MS;
var cs = target.connectStart;
if (orig.connectEnd <= cs) orig.connectEnd = cs + TCP_MS + TLS_MS;
if (orig.secureConnectionStart === 0 || orig.secureConnectionStart === undefined) {
orig.secureConnectionStart = cs + TCP_MS;
}
if (orig.nextHopProtocol === "" || orig.nextHopProtocol === undefined) {
orig.nextHopProtocol = DEFAULT_PROTOCOL;
}
return orig;
};
}
return Reflect.get(target, prop, receiver);
},
});
}
var origByType = performance.getEntriesByType;
function getEntriesByType(type) {
var r = __mochi_apply__.call(origByType, this, [type]);
if (type === "navigation" && Array.isArray(r)) {
return r.map(patchEntry);
}
return r;
}
__mochi_register_native__(getEntriesByType, "getEntriesByType");
var origGetEntries = performance.getEntries;
function getEntries() {
var r = __mochi_apply__.call(origGetEntries, this, []);
if (Array.isArray(r)) {
return r.map(patchEntry);
}
return r;
}
__mochi_register_native__(getEntries, "getEntries");
var origByName = performance.getEntriesByName;
function getEntriesByName(name, type) {
var r = (type === undefined)
? __mochi_apply__.call(origByName, this, [name])
: __mochi_apply__.call(origByName, this, [name, type]);
if (Array.isArray(r)) {
return r.map(patchEntry);
}
return r;
}
__mochi_register_native__(getEntriesByName, "getEntriesByName");
try {
__mochi_defineProperty__(performance, "getEntriesByType", {
configurable: true, enumerable: false, writable: true, value: getEntriesByType,
});
__mochi_defineProperty__(performance, "getEntries", {
configurable: true, enumerable: false, writable: true, value: getEntries,
});
__mochi_defineProperty__(performance, "getEntriesByName", {
configurable: true, enumerable: false, writable: true, value: getEntriesByName,
});
} catch (_e) {}
})();
`;
}
+1
-1
{
"name": "@mochi.js/inject",
"version": "0.3.1",
"version": "0.4.0",
"description": "Zero-jitter stealth payload for mochi — JIT-friendly proxies installed before any page script.",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -38,2 +38,3 @@ /**

import { emitNetworkInfoModule } from "./modules/network-info";
import { emitPerformanceTimingModule } from "./modules/performance-timing";
import { emitPermissionsModule } from "./modules/permissions";

@@ -113,2 +114,3 @@ import { emitPluginsModule } from "./modules/plugins";

parts.push(wrapTry("network-info", emitNetworkInfoModule(matrix)));
parts.push(wrapTry("performance-timing", emitPerformanceTimingModule(matrix)));
parts.push(wrapTry("permissions", emitPermissionsModule(matrix)));

@@ -115,0 +117,0 @@ parts.push(wrapTry("screen-orientation", emitScreenOrientationModule(matrix)));