Skip to content

Commit e6ec07c

Browse files
feat: enhance security and error handling in iframe communication and bootstrap processes
1 parent c5b679d commit e6ec07c

8 files changed

Lines changed: 111 additions & 100 deletions

File tree

apps/browser-demo/src/App.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ body {
282282
border-color: var(--accent);
283283
}
284284

285+
.code-textarea:focus-visible {
286+
outline: 2px solid var(--accent);
287+
outline-offset: -2px;
288+
}
289+
285290
.code-textarea:disabled {
286291
opacity: 0.6;
287292
}

apps/browser-demo/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function App() {
5858
setResult({
5959
success: false,
6060
error: {
61-
name: 'AppError',
61+
name: err instanceof Error ? err.name : 'AppError',
6262
message: err instanceof Error ? err.message : String(err),
6363
},
6464
stats: zeroStats,

libs/browser/e2e/fixtures/debug-inner.html

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@
8989

9090
if (blockedPropertiesSet.has(propName)) {
9191
if (isNonConfigurable) return Reflect.get(target, property, receiver);
92-
if (true) {
93-
throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");
94-
} else {
95-
return undefined;
96-
}
92+
throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");
9793
}
9894

9995
var value = Reflect.get(target, property, receiver);
@@ -109,11 +105,7 @@
109105
set: function (target, property, value, receiver) {
110106
var propName = String(property);
111107
if (blockedPropertiesSet.has(propName)) {
112-
if (true) {
113-
throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");
114-
} else {
115-
return false;
116-
}
108+
throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");
117109
}
118110
return Reflect.set(target, property, value, receiver);
119111
},
@@ -143,6 +135,7 @@
143135
window.addEventListener('message', function (event) {
144136
var data = event.data;
145137
if (!data || data.__enclave_msg__ !== true) return;
138+
if (data.requestId && data.requestId !== requestId) return;
146139

147140
if (data.type === 'tool-response') {
148141
var pending = pendingToolCalls[data.callId];
@@ -411,32 +404,6 @@
411404
}
412405
})();
413406

414-
// Freeze all prototypes
415-
(function () {
416-
var protos = [
417-
Object.prototype,
418-
Array.prototype,
419-
Function.prototype,
420-
String.prototype,
421-
Number.prototype,
422-
Boolean.prototype,
423-
Date.prototype,
424-
Error.prototype,
425-
TypeError.prototype,
426-
RangeError.prototype,
427-
SyntaxError.prototype,
428-
ReferenceError.prototype,
429-
URIError.prototype,
430-
EvalError.prototype,
431-
Promise.prototype,
432-
];
433-
for (var i = 0; i < protos.length; i++) {
434-
try {
435-
Object.freeze(protos[i]);
436-
} catch (e) {}
437-
}
438-
})();
439-
440407
// Memory-safe prototype patches
441408
(function () {
442409
var ml = 1048576;
@@ -508,6 +475,32 @@
508475
} catch (e) {}
509476
})();
510477

478+
// Freeze all prototypes
479+
(function () {
480+
var protos = [
481+
Object.prototype,
482+
Array.prototype,
483+
Function.prototype,
484+
String.prototype,
485+
Number.prototype,
486+
Boolean.prototype,
487+
Date.prototype,
488+
Error.prototype,
489+
TypeError.prototype,
490+
RangeError.prototype,
491+
SyntaxError.prototype,
492+
ReferenceError.prototype,
493+
URIError.prototype,
494+
EvalError.prototype,
495+
Promise.prototype,
496+
];
497+
for (var i = 0; i < protos.length; i++) {
498+
try {
499+
Object.freeze(protos[i]);
500+
} catch (e) {}
501+
}
502+
})();
503+
511504
// ============================================================
512505
// Remove Dangerous Globals
513506
// ============================================================
@@ -613,6 +606,8 @@
613606
})(blockedObjMethods[bm]);
614607
}
615608

609+
var _defineProperty = Object.defineProperty;
610+
616611
// Neutralize dangerous static methods on intrinsic Object
617612
(function () {
618613
var RealObject = Object.getPrototypeOf({}).constructor;
@@ -683,7 +678,7 @@
683678
for (var gKey in safeGlobals) {
684679
if (safeGlobals.hasOwnProperty(gKey)) {
685680
try {
686-
Object.defineProperty(window, gKey, {
681+
_defineProperty(window, gKey, {
687682
value: safeGlobals[gKey],
688683
writable: false,
689684
configurable: false,

libs/browser/e2e/fixtures/debug-inner2.html

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@
8989

9090
if (blockedPropertiesSet.has(propName)) {
9191
if (isNonConfigurable) return Reflect.get(target, property, receiver);
92-
if (true) {
93-
throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");
94-
} else {
95-
return undefined;
96-
}
92+
throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");
9793
}
9894

9995
var value = Reflect.get(target, property, receiver);
@@ -109,11 +105,7 @@
109105
set: function (target, property, value, receiver) {
110106
var propName = String(property);
111107
if (blockedPropertiesSet.has(propName)) {
112-
if (true) {
113-
throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");
114-
} else {
115-
return false;
116-
}
108+
throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");
117109
}
118110
return Reflect.set(target, property, value, receiver);
119111
},
@@ -143,6 +135,7 @@
143135
window.addEventListener('message', function (event) {
144136
var data = event.data;
145137
if (!data || data.__enclave_msg__ !== true) return;
138+
if (data.requestId && data.requestId !== requestId) return;
146139

147140
if (data.type === 'tool-response') {
148141
var pending = pendingToolCalls[data.callId];
@@ -411,32 +404,6 @@
411404
}
412405
})();
413406

414-
// Freeze all prototypes
415-
(function () {
416-
var protos = [
417-
Object.prototype,
418-
Array.prototype,
419-
Function.prototype,
420-
String.prototype,
421-
Number.prototype,
422-
Boolean.prototype,
423-
Date.prototype,
424-
Error.prototype,
425-
TypeError.prototype,
426-
RangeError.prototype,
427-
SyntaxError.prototype,
428-
ReferenceError.prototype,
429-
URIError.prototype,
430-
EvalError.prototype,
431-
Promise.prototype,
432-
];
433-
for (var i = 0; i < protos.length; i++) {
434-
try {
435-
Object.freeze(protos[i]);
436-
} catch (e) {}
437-
}
438-
})();
439-
440407
// Memory-safe prototype patches
441408
(function () {
442409
var ml = 1048576;
@@ -508,6 +475,32 @@
508475
} catch (e) {}
509476
})();
510477

478+
// Freeze all prototypes
479+
(function () {
480+
var protos = [
481+
Object.prototype,
482+
Array.prototype,
483+
Function.prototype,
484+
String.prototype,
485+
Number.prototype,
486+
Boolean.prototype,
487+
Date.prototype,
488+
Error.prototype,
489+
TypeError.prototype,
490+
RangeError.prototype,
491+
SyntaxError.prototype,
492+
ReferenceError.prototype,
493+
URIError.prototype,
494+
EvalError.prototype,
495+
Promise.prototype,
496+
];
497+
for (var i = 0; i < protos.length; i++) {
498+
try {
499+
Object.freeze(protos[i]);
500+
} catch (e) {}
501+
}
502+
})();
503+
511504
// ============================================================
512505
// Remove Dangerous Globals
513506
// ============================================================

libs/browser/e2e/fixtures/debug-outer.html

Lines changed: 10 additions & 1 deletion
Large diffs are not rendered by default.

libs/browser/src/adapters/inner-iframe-bootstrap.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
7979
var _getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
8080
var _ReflectGet = typeof Reflect !== 'undefined' ? Reflect.get : function(t,p) { return t[p]; };
8181
var _ReflectSet = typeof Reflect !== 'undefined' ? Reflect.set : function(t,p,v) { t[p]=v; return true; };
82+
var _Proxy = typeof Proxy !== 'undefined' ? Proxy : undefined;
8283
8384
var blockedPropertiesSet = new Set(${blockedProperties});
8485
var proxyCache = new WeakMap();
@@ -89,7 +90,7 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
8990
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) return obj;
9091
if (proxyCache.has(obj)) return proxyCache.get(obj);
9192
92-
var proxy = new Proxy(obj, {
93+
var proxy = new _Proxy(obj, {
9394
get: function(target, property, receiver) {
9495
var propName = String(property);
9596
var descriptor = _getOwnPropertyDescriptor(target, property);
@@ -101,10 +102,10 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
101102
102103
if (blockedPropertiesSet.has(propName)) {
103104
if (isNonConfigurable) return _ReflectGet(target, property, receiver);
104-
if (${throwOnBlocked}) {
105-
throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");
106-
} else {
107-
return undefined;
105+
${
106+
throwOnBlocked
107+
? `throw createSafeError("Security violation: Access to '" + propName + "' is blocked.");`
108+
: `return undefined;`
108109
}
109110
}
110111
@@ -121,10 +122,10 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
121122
set: function(target, property, value, receiver) {
122123
var propName = String(property);
123124
if (blockedPropertiesSet.has(propName)) {
124-
if (${throwOnBlocked}) {
125-
throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");
126-
} else {
127-
return false;
125+
${
126+
throwOnBlocked
127+
? `throw createSafeError("Security violation: Setting '" + propName + "' is blocked.");`
128+
: `return false;`
128129
}
129130
}
130131
return _ReflectSet(target, property, value, receiver);
@@ -151,6 +152,7 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
151152
window.addEventListener('message', function(event) {
152153
var data = event.data;
153154
if (!data || data.__enclave_msg__ !== true) return;
155+
if (data.requestId && data.requestId !== requestId) return;
154156
155157
if (data.type === 'tool-response') {
156158
var pending = pendingToolCalls[data.callId];
@@ -386,21 +388,7 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
386388
}
387389
})();
388390
389-
// Freeze all prototypes
390-
(function() {
391-
var protos = [
392-
Object.prototype, Array.prototype, Function.prototype,
393-
String.prototype, Number.prototype, Boolean.prototype,
394-
Date.prototype, Error.prototype, TypeError.prototype,
395-
RangeError.prototype, SyntaxError.prototype, ReferenceError.prototype,
396-
URIError.prototype, EvalError.prototype, Promise.prototype
397-
];
398-
for (var i = 0; i < protos.length; i++) {
399-
try { Object.freeze(protos[i]); } catch(e) {}
400-
}
401-
})();
402-
403-
// Memory-safe prototype patches
391+
// Memory-safe prototype patches (must run BEFORE freeze)
404392
(function() {
405393
var ml = ${memoryLimit};
406394
if (ml <= 0) return;
@@ -457,6 +445,20 @@ function generateInnerIframeScript(userCode: string, config: SerializedIframeCon
457445
} catch(e) {}
458446
})();
459447
448+
// Freeze all prototypes
449+
(function() {
450+
var protos = [
451+
Object.prototype, Array.prototype, Function.prototype,
452+
String.prototype, Number.prototype, Boolean.prototype,
453+
Date.prototype, Error.prototype, TypeError.prototype,
454+
RangeError.prototype, SyntaxError.prototype, ReferenceError.prototype,
455+
URIError.prototype, EvalError.prototype, Promise.prototype
456+
];
457+
for (var i = 0; i < protos.length; i++) {
458+
try { Object.freeze(protos[i]); } catch(e) {}
459+
}
460+
})();
461+
460462
// ============================================================
461463
// Remove Dangerous Globals
462464
// ============================================================

libs/browser/src/adapters/outer-iframe-bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ function generateOuterIframeScript(options: OuterIframeBootstrapOptions): string
197197
198198
var fromInner = (innerFrame && event.source === innerFrame.contentWindow);
199199
var fromHost = (event.source === window.parent);
200+
if (data.requestId && data.requestId !== requestId) return;
200201
201202
// Messages from inner iframe (tool-call, result, console)
202203
if (data.type === 'tool-call') {

libs/browser/src/browser-enclave.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ export class BrowserEnclave {
311311
doubleIframeConfig: this.doubleIframeConfig,
312312
secureProxyConfig: this.config.secureProxyConfig,
313313
blockedProperties: serializedConfig.blockedProperties,
314+
// TODO: Custom SuspiciousPattern[] from config are not currently serialized and
315+
// injected. Proper support would require an API that accepts
316+
// SerializableSuspiciousPattern[] (string bodies) or a serialization bridge.
314317
suspiciousPatterns: DEFAULT_SERIALIZED_PATTERNS,
315318
validationConfig,
316319
};
@@ -422,7 +425,10 @@ export class BrowserEnclave {
422425
parentValidation: {
423426
...DEFAULT_DOUBLE_IFRAME_CONFIG.parentValidation,
424427
...options.parentValidation,
425-
suspiciousPatterns: [...(options.parentValidation?.suspiciousPatterns ?? [])],
428+
suspiciousPatterns: [
429+
...DEFAULT_DOUBLE_IFRAME_CONFIG.parentValidation.suspiciousPatterns,
430+
...(options.parentValidation?.suspiciousPatterns ?? []),
431+
],
426432
},
427433
};
428434
}

0 commit comments

Comments
 (0)