Skip to content

Commit 0a6daee

Browse files
committed
feat: US-111 - Fix timing mitigation — make Date.now non-configurable and patch Date constructor
1 parent 952f8f0 commit 0a6daee

3 files changed

Lines changed: 166 additions & 18 deletions

File tree

packages/secure-exec-core/isolate-runtime/src/inject/apply-timing-mitigation-freeze.ts

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,97 @@ const __frozenTimeMs =
99
: Date.now();
1010
const __frozenDateNow = () => __frozenTimeMs;
1111

12+
// Freeze Date.now — non-configurable + non-writable prevents sandbox override
1213
try {
1314
Object.defineProperty(Date, "now", {
1415
value: __frozenDateNow,
15-
configurable: true,
16-
writable: true,
16+
configurable: false,
17+
writable: false,
1718
});
1819
} catch {
1920
Date.now = __frozenDateNow;
2021
}
2122

23+
// Patch Date constructor so new Date().getTime() returns degraded time
24+
const __OrigDate = Date;
25+
const __FrozenDate = function Date(
26+
this: InstanceType<DateConstructor>,
27+
...args: unknown[]
28+
) {
29+
if (new.target) {
30+
// Called with new — no-arg returns frozen time, with args passes through
31+
if (args.length === 0) {
32+
return new __OrigDate(__frozenTimeMs);
33+
}
34+
// @ts-expect-error — spread forwarding to variadic Date constructor
35+
return new __OrigDate(...args);
36+
}
37+
// Called without new — Date() returns string like original
38+
return __OrigDate();
39+
} as unknown as DateConstructor;
40+
Object.defineProperty(__FrozenDate, "prototype", {
41+
value: __OrigDate.prototype,
42+
writable: false,
43+
configurable: false,
44+
});
45+
__FrozenDate.now = __frozenDateNow;
46+
__FrozenDate.parse = __OrigDate.parse;
47+
__FrozenDate.UTC = __OrigDate.UTC;
48+
// Lock Date.now on the replacement constructor too
49+
Object.defineProperty(__FrozenDate, "now", {
50+
value: __frozenDateNow,
51+
configurable: false,
52+
writable: false,
53+
});
54+
try {
55+
Object.defineProperty(globalThis, "Date", {
56+
value: __FrozenDate,
57+
configurable: false,
58+
writable: false,
59+
});
60+
} catch {
61+
(globalThis as Record<string, unknown>).Date = __FrozenDate;
62+
}
63+
64+
/* Replace globalThis.performance with a frozen proxy — native V8 performance
65+
may have non-configurable properties that prevent in-place freezing. */
2266
const __frozenPerformanceNow = () => 0;
23-
const __performance = globalThis.performance;
24-
if (typeof __performance !== "undefined" && __performance !== null) {
25-
try {
26-
Object.defineProperty(__performance, "now", {
27-
value: __frozenPerformanceNow,
28-
configurable: true,
29-
writable: true,
30-
});
31-
} catch {
32-
try {
33-
Object.assign(__performance, { now: __frozenPerformanceNow });
34-
} catch {}
67+
const __origPerf = globalThis.performance;
68+
const __frozenPerf = Object.create(null) as Record<string, unknown>;
69+
// Copy existing methods/properties, override now()
70+
if (typeof __origPerf !== "undefined" && __origPerf !== null) {
71+
const src = __origPerf as unknown as Record<string, unknown>;
72+
for (const key of Object.getOwnPropertyNames(
73+
Object.getPrototypeOf(__origPerf) ?? __origPerf,
74+
)) {
75+
if (key !== "now") {
76+
try {
77+
const val = src[key];
78+
if (typeof val === "function") {
79+
__frozenPerf[key] = val.bind(__origPerf);
80+
} else {
81+
__frozenPerf[key] = val;
82+
}
83+
} catch {
84+
/* skip inaccessible properties */
85+
}
86+
}
3587
}
36-
} else {
37-
setGlobalValue("performance", {
38-
now: __frozenPerformanceNow,
88+
}
89+
Object.defineProperty(__frozenPerf, "now", {
90+
value: __frozenPerformanceNow,
91+
configurable: false,
92+
writable: false,
93+
});
94+
Object.freeze(__frozenPerf);
95+
try {
96+
Object.defineProperty(globalThis, "performance", {
97+
value: __frozenPerf,
98+
configurable: false,
99+
writable: false,
39100
});
101+
} catch {
102+
(globalThis as Record<string, unknown>).performance = __frozenPerf;
40103
}
41104

42105
/* Harden SharedArrayBuffer removal — neuter prototype so saved refs are useless,

0 commit comments

Comments
 (0)