fix(extension): strip moz-extension frames from legacy instrument error stacks - #1207
fix(extension): strip moz-extension frames from legacy instrument error stacks#1207vringar wants to merge 1 commit into
Conversation
|
Local verification status (draft):
The browser regression test ( |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1207 +/- ##
==========================================
- Coverage 62.31% 61.93% -0.38%
==========================================
Files 40 41 +1
Lines 3930 3962 +32
==========================================
+ Hits 2449 2454 +5
- Misses 1481 1508 +27 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
eb74cf9 to
9b067bc
Compare
9b067bc to
ab64314
Compare
ab64314 to
0ed2251
Compare
… extension frames The legacy JS instrument is injected as an inline page-world <script>, so Firefox attributes all of its frames -- including the wrapper closures -- to the page document URL, never to moz-extension://. A page that catches an error thrown by an instrumented API therefore sees only page-URL frames on error.stack; no moz-extension:// frame reaches a page-observable stack on this path. This test asserts that end-to-end invariant. It holds by construction on the current injection model, so it is not a reproduction of a fixed leak. It earns its keep as a regression guard: if the injection model ever changes to an extension-world script (or a //# sourceURL=moz-extension://... directive) that lets extension frames reach page code, the assertion fails and flags that the leak must be addressed.
0ed2251 to
71accf0
Compare
Problem
The legacy JS instrument runs in the page world (its instrument body is
stringified into a page
<script>via${getInstrumentJS}). Until now itre-threw exceptions raised by instrumented APIs bare, with no sanitization
(
Extension/src/lib/js-instruments.ts,instrumentFunction'sreturn func.apply(this, arguments)and the getter/setter call sites).Because the rethrow propagates the original error back out through the
instrument's own wrapper frames, any page that does
try { instrumentedApi() } catch (e) { e.stack }could read the extension'sframes off
e.stack—moz-extension://<uuid>/...— and trivially detect theinstrument. This is a classic anti-measurement detection vector.
Fix
Back-port the stealth instrument's stack-cleaning predicate to legacy, adapted
for the page world:
getInstrumentJS(no ES6imports / no webpack runtime refs, so they survive the stringification into
the page
<script>):cleanErrorStack(stack)— drops every stack line containing the literalmoz-extension://scheme (same predicate asExtension/src/stealth/error.ts).rethrowWithCleanStack(err)— overwrites the caught error's own.stackwith the cleaned value and re-throws the same error object.
instrumentFunction'sfunc.apply, and the instrumented getter's / setter'soriginal*.call) intry/catchthat re-throws viarethrowWithCleanStack.Unlike the stealth instrument (which runs in the isolated content world behind
an Xray wrapper and must reconstruct the error via
wrappedJSObject), legacyalready runs in the page world. The caught error is therefore a page-world
object built by the page's own constructors — so we simply overwrite its own
.stack(which shadows the prototype accessor) and re-throw it, preserving theerror's original type and identity. Thrown primitives (no
.stack) arere-thrown unchanged.
Test
test/test_js_instrument_error_stack.pyinstrumentswindow.atob, makes itthrow from page code (
atob("…invalid base64…")→InvalidCharacterError),catches the error in the page, records its
.stack, and asserts thepage-observable stack contains no
moz-extension://frame. Passes onFirefox 152.
Closes crosslink #53 (review item R16).