Strip debug globals from stealth_inject.zig#2
Open
hunterjsb wants to merge 54 commits into
Open
Conversation
…ion (Phase 0) Make Lightpanda's JS fingerprint indistinguishable from Chrome 131: - Default User-Agent changed from "Lightpanda/1.0" to Chrome 131 UA string - navigator.vendor → "Google Inc.", appVersion derived from UA - navigator.plugins returns 5 Chrome PDF plugins with name/description/filename - Screen, Window, VisualViewport dimensions configurable via --screen-width/--screen-height - canvas.toDataURL() returns valid PNG data URL (placeholder) - AudioContext constructor added (stub with state/sampleRate/baseLatency) All 20 fingerprint tests pass via CDP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace upstream Lightpanda README with StealthPanda-specific docs: fingerprint coverage table, phased roadmap, ASCII before/after diagram. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop CLA, CONTRIBUTING, LICENSING, SECURITY, Dockerfile, and CI workflows that reference Lightpanda's AWS/Slack/Docker secrets. Keep zig-test.yml for basic CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ent Hints (Phase 1) - Add ssl_cipher_list, tls13_ciphers, ssl_ec_curves, http_version curl options - Create TlsProfile system with chrome_131 and firefox_133 profiles - Apply Chrome TLS profile by default (cipher order, EC curves, HTTP/2) - Add sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform Client Hints headers - Add --tls-profile CLI flag (chrome, firefox) Verified via tls.peet.ws: JA3 92c1dfe30a246d49df83ee297f44044a, JA4 t13d1510h2_8daaf6152771_95ca0cbbc74b, HTTP/2 active. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…prevent bot detection crashes bot.sannysoft.com was crashing the CDP session because drawImage was missing from CanvasRenderingContext2D. Added as no-ops along with sendBeacon on Navigator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add window.chrome object with chrome.runtime (fixes sannysoft Chrome check) - WebGL getParameter returns realistic Chrome/NVIDIA values for 18 params including UNMASKED_VENDOR/RENDERER (previously returned empty strings) bot.sannysoft.com: all 15 key checks now pass including WebGL vendor/renderer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Integrate z2d pure-Zig 2D graphics library - Canvas fillRect/strokeRect/fillText now render actual pixels via z2d - toDataURL() encodes real surface pixels as PNG (was returning static placeholder) - Different canvas drawings produce unique data URLs (canvas fingerprinting works) - Custom minimal PNG encoder with zlib store-only compression Verified: red vs blue fillRect produce different toDataURL() outputs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-inject anti-detection script before page scripts on every navigation - Lock navigator.webdriver to false (non-configurable) - Clean cdc_ / __webdriver properties from window - Patch Permissions.query for notifications - Add window.chrome with chrome.runtime stub All 7 stealth suite tests pass. No CDP artifacts detected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IFrame.setSrc (property setter) triggered navigation via
iframeAddedCallback, but setAttribute("src", ...) did not because
IFrame had no attributeChange handler. Turnstile uses setAttribute
to set the challenge iframe's src.
Added Build.attributeChange to IFrame that triggers iframeAddedCallback
when the src attribute changes, matching the behavior of the .src
property setter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two critical fixes for cross-origin iframe script execution:
1. Document.write/open/close: resolve the owning page for the target
document instead of using the caller's page. When parent calls
iframe.contentDocument.write(), scripts now execute in the iframe's
V8 context, not the parent's.
2. scriptAddedCallback: resolve the correct page for dynamically-created
script elements. When an iframe's script does document.createElement
("script") + appendChild, the new script now runs in the iframe's
context instead of the parent's.
Both fixes use resolveOwningPage/resolvePageForNode to find the frame
page that owns the target document.
Verified: iframe.__step1 and __step2 both set correctly, nothing leaks
to parent window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…TTPS
- matchMedia("(min-width: 1px)") now returns true (was always false)
- Implements basic media query evaluation for min-width, max-width,
prefers-color-scheme, screen/print
- isSecureContext is true for HTTPS pages (was hardcoded false)
These fixes eliminate the "unsupported_browser" rejection from Cloudflare
Turnstile's challenge script. The Turnstile widget now initializes and
sends heartbeat events instead of rejecting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
setAttribute("src", sameUrl) was triggering navigation on every call,
even with the same URL. Turnstile sets src thousands of times while
polling, causing iframe recreation and widget registry corruption.
Now only navigates when src actually changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MessageEvent.getSource now returns Window.Access (matching contentWindow behavior) instead of raw *Window. This enables correct event.source === iframe.contentWindow comparison for cross-origin message routing. Also adds std import to IFrame.zig for dedup comparison. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
about:blank iframes created within HTTPS pages now correctly report isSecureContext = true, matching real browser behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- window.origin returns page origin (about:blank inherits from parent)
- window.crossOriginIsolated returns false
- postMessage source uses caller's page.window instead of getIncumbent
Turnstile status: challenge validates ("ok"), heartbeats flow, but
requestExtraParams goes unanswered because event.source !== iframe.contentWindow
(different Window object pointers for the same browsing context).
Root cause: Lightpanda lacks WindowProxy — a stable identity that
survives iframe navigation. This is needed for event.source matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts to using getIncumbent() for event.source in postMessage. When iframe calls parent.postMessage(), the V8 current context is the parent's (where postMessage is defined), but the incumbent context is the iframe's (which initiated the call). getIncumbent correctly returns the iframe's window. Verified: event.source === iframe.contentWindow is now true for same-origin iframes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MessageEvent.getSource now wraps through Window.Access, which returns CrossOriginWindow for cross-origin sources. This enables postMessage to work on event.source for cross-origin iframes (was getting "no access" error because raw Window access was blocked by V8 security check). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…angs) Returning ?Window.Access from getSource caused page navigation to hang. The bridge doesn't handle optional union return types correctly. Reverted to returning raw *Window. The event.source matching problem needs a different approach — possibly storing the CrossOriginWindow wrapper directly in MessageEvent instead of wrapping at access time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add postMessage interception in stealth inject that blocks
{event: "reject", reason: "unsupported_browser"} messages from
challenge iframes. This prevents Turnstile's parent code from
cleaning up the working widget.
Also adds window.origin (inherits from parent for about:blank)
and window.crossOriginIsolated properties.
Rejects reduced from 8 to 4 (some fire before stealth inject runs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added capture-phase message listener in stealth inject that blocks reject events with reason "unsupported_browser" before they reach the Turnstile API's handler. This prevents widget cleanup. Rejects now 0 (was 8). Shadow DOM widget is created (closed mode). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Turnstile's implicit render doesn't fire because setTimeout(Ar, 0) for the ready callback doesn't execute during async script loading. Added polling that detects when turnstile API is available and manually calls render() on .cf-turnstile elements. Widget now gets registered in widgetMap, but challenge flow still stalls on requestExtraParams response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added message handler in stealth inject that responds to requestExtraParams with the expected extraParams format, using iframe.contentWindow.postMessage for cross-origin delivery. Also added force render polling for Turnstile implicit render. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Capture nextRcV from challenge iframe's init message and include it in the extraParams response. The handler successfully sends postMessage to the iframe (18 successful sends, 0 errors), but the challenge still doesn't complete — likely missing required fields in the extraParams format (ch, chlPageData, au, wPr, timing fields). Status: 0 rejects, 12 inits, 12 requestExtraParams, messages flowing, CF returns "ok" to proof-of-work. Need exact extraParams format. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
event.source now always returns CrossOriginWindow wrapper, which allows postMessage() to work on the source window regardless of origin. This matches the HTML spec's WindowProxy behavior where event.source should always support postMessage. Also pub-ified CrossOriginWindow and updated stealth inject to cache event.source from init messages and use it to respond to requestExtraParams with the correct widget's source window. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verified via debug markers that cached.source.postMessage() IS called and completes without error for all 4 Turnstile widgets. The message reaches the challenge iframe but is rejected because our extraParams format is missing required internal fields (ch, chlPageData, au, wPr). The Turnstile parent code needs to generate these fields itself. Next: fix the Turnstile parent handler's shadow DOM iframe lookup so its own requestExtraParams handler can find Ce and post back. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed custom requestExtraParams handler. Let Turnstile parent code handle the response natively. Force render via polling still active. Shadow DOM querySelector works correctly in isolation — iframe is found by ID, contentWindow exists. The Turnstile handler should work but still doesn't call postMessage. Investigating. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed reject blocking and force render. Turnstile now renders naturally via its async script path (setTimeout(Wt, 0)). Key finding: with latest changes, ZERO "unsupported_browser" rejects! The browser features now pass CF's checks. But widgetMap.has(widgetId) returns false despite widgets being visible in DOM. The Turnstile render creates DOM elements but widgetMap insertion may fail silently. Shadow DOM querySelector is never called because the handler's widgetMap lookup gates all processing. Next: investigate why widgetMap.set(Ne, ...) doesn't persist the widget during async render. Possible causes: error in widget creation that clears the entry, or the async render timing issue. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an element with a shadow root is inserted into the document, also call nodeIsReady on shadow root children. Without this, iframes inside closed shadow DOMs never get iframeAddedCallback and never get a browsing context. This fixes the Turnstile flow where the challenge iframe is created inside a closed shadow root, then the wrapper div is appended to the document. Previously the iframe was invisible to Lightpanda's lifecycle management. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Re-added unsupported_browser reject blocking in both parent (capture phase) and iframe (postMessage override) contexts. Combined with the shadow root child processing fix. Zero rejects achieved. Challenge iframes now properly processed. widgetMap lookup still fails for requestExtraParams — the iframe's widgetId doesn't match registered widgets. overrunBegin events from the watchcat DO match, confirming widgets exist but with different IDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an element with a shadow root is inserted during parsing and becomes connected, also process shadow root children via nodeIsReady. This mirrors the fix on the non-parser path. Note: the parser path's isConnected() check may return false during parsing since nodes aren't fully connected until documentIsComplete. This needs further investigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed isConnected()/isInShadowTree() checks from setSrc and attributeChange. Iframes in detached shadow DOMs (like Turnstile's closed shadow root) now get iframeAddedCallback immediately when their src is set, even before the shadow host is connected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restore isConnected check in setSrc/attributeChange to prevent errors during Turnstile render (which would cause widget removal from map). Add deferred iframe.src trigger via Promise.resolve().then() in stealth inject to set src after shadow host is connected. Also includes parser-path shadow root child processing fix. Investigation ongoing: widgetMap has widgets (overrunBegin fires) but handler doesn't process requestExtraParams for the same widgetId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Confirmed: - event.origin is "https://challenges.cloudflare.com" (correct, in lr) - widgetMap has the widget (turnstile.isExpired returns false) - Jr origin check passes - Handler should enter switch and process requestExtraParams - But shadow.querySelector is never called (zero sq entries) The handler has: Jr check → widgetMap.has → get widget → switch(event) All gates pass but requestExtraParams case never executes. Possible: live Turnstile version differs from cached analysis, or there's an additional gate we haven't found. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrapped window.addEventListener to catch errors in message handlers. Found: wrapper never catches any handler registration — the Turnstile code's addEventListener call doesn't go through our wrapper. This suggests addScriptToEvaluateOnNewDocument scripts run in a different context than page scripts, OR the Turnstile async script somehow registers its handler before our wrapper is installed. All verification passes: origin correct, widgetMap has widget, getElementById works in shadow roots. The final gap is between the handler's widgetMap check and shadow.querySelector — something in between throws silently. 35 commits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved stealth inject from CDP pageNavigated handler to Env.createContext. This ensures patches (addEventListener wrapper, navigator.webdriver, etc.) are installed BEFORE any scripts execute, including async scripts. Verified: addEventListener IS wrapped (toString shows wrapper function), but Turnstile handler still processes requestExtraParams without calling shadow.querySelector. No errors thrown. The handler silently skips the case body. The Turnstile handler receives the message, enters the widgetMap check, but something inside the requestExtraParams case prevents execution of shadow.querySelector. Zero handler errors caught. 37 commits total. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed addEventListener wrapper from window.addEventListener to EventTarget.prototype.addEventListener. Now successfully wraps the Turnstile handler (verified: __tsWrapped = true). Key finding: handler runs WITHOUT errors for requestExtraParams but still doesn't call shadow.querySelector. The case body silently does nothing — no throw, no querySelector call. The Turnstile code is identical between our analysis and live (same MD5). The requestExtraParams case should call n.shadow.querySelector but doesn't. This remains unexplained. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… is null The Turnstile handler DOES process requestExtraParams: - "entering handler" and "handler returned" logged without errors - querySelector finds the iframe in shadow DOM - BUT Ce.contentWindow is null (no browsing context) - The optional chaining (Ie = Ce.contentWindow) === null skips postMessage ROOT CAUSE CONFIRMED: iframeAddedCallback never fires for the shadow DOM iframe. The iframe exists in the DOM with correct ID but has no Window/Page. Our deferred this.src = value via Promise.resolve().then() doesn't trigger iframeAddedCallback. Need: a way to trigger iframeAddedCallback for iframes that were inserted into shadow DOMs while detached, AFTER the shadow host connects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only trigger iframe.src once, only when isConnected && !contentWindow. Prevents multiple iframeAddedCallback calls that could cause navigation race conditions. Root cause confirmed: handler's Ce.contentWindow is null because iframeAddedCallback hasn't created a Page for the shadow DOM iframe. The deferred trigger fires with isConnected=true but setSrc still doesn't successfully create the browsing context. 40 commits total. The Turnstile flow is 99% working — just needs the shadow DOM iframe to get a valid contentWindow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Definitive finding: the deferred iframe.src = value assignment: - Element is alive, connected, correct IFRAME tag - src property descriptor exists on prototype (srcDesc:object) - Assignment runs without JS error - But IFrame.setSrc in Zig is NEVER called The V8 setter callback for .src fires but the Zig dispatcher doesn't reach setSrc. This is likely a bridge dispatch issue where the V8 callback fails to extract the correct page/context for elements inside shadow DOMs or elements captured in closures from setTimeout callbacks. Next: investigate the bridge's property setter dispatch for iframes inside shadow DOMs. Check if Caller.init fails to extract context from the V8 isolate for elements in closed shadow roots. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ROOT CAUSE FOUND AND FIXED: shadowRoot.appendChild() in V8 didn't reach Zig's Node.appendChild because DocumentFragment's JsApi didn't include appendChild. V8's prototype chain resolution stopped at DocumentFragment and used a V8-native appendChild that bypassed Zig. Added explicit appendChild to DocumentFragment.JsApi that delegates to Node.appendChild. This ensures iframes appended to shadow roots (like Turnstile's closed shadow DOM) get proper iframeAddedCallback and contentWindow initialization. Verified: shadow DOM iframe now has contentWindow = true (was null). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make DocumentFragment.appendChild swallow errors to prevent Turnstile render from failing when iframeAddedCallback throws during shadow DOM iframe initialization. The root cause fix (DocumentFragment.appendChild in JsApi) is correct — shadow DOM iframes now get contentWindow in manual tests. But the Turnstile flow still doesn't produce a token due to additional issues in the handler's requestExtraParams processing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extended the V8 prototype chain fix to include all Node mutation methods on DocumentFragment. This ensures shadow DOM operations (insertBefore, removeChild, replaceChild) go through the Zig bridge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gging Restored dom_exception error propagation in DocumentFragment.appendChild (was swallowing errors which could cause silent failures). Added postMessage logging to track message flow. KEY FINDING: Turnstile handler logs "Ignored message from wrong origin: null" The challenge iframe's messages have origin=null instead of https://challenges.cloudflare.com. This causes the Jr() origin check to reject all messages from the challenge iframe. The null origin likely comes from about:blank iframes or from iframes where the origin wasn't properly set during navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The postMessage origin was derived from source_window._location.getOrigin() which returns "null" for about:blank iframes. Changed to use source_page.origin which is properly set during navigation. This fixes the "Ignored message from wrong origin: null" error in Turnstile's handler that was dropping ALL messages from challenge iframes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rame) getIncumbent() returns parent context for about:blank iframes that navigated cross-origin. Origin in postMessage is nowsecure.nl instead of challenges.cloudflare.com. Turnstile drops ALL messages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an iframe calls parent.postMessage(), the origin must be the iframe's origin, not the parent's. V8's GetIncumbentContext() returns the parent context for same-origin iframes, giving wrong origin. Fixed by capturing the caller's page in Window.getParent() (which runs in the iframe's context) and using it in postMessage for origin. VERIFIED: zero "Ignored message from wrong origin" warnings! Events now include food heartbeats (168), no overrunBegin. The Turnstile handler processes messages correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Origin fix confirmed: zero "wrong origin" warnings, heartbeats flow. New event: widgetStale:8 after 125s (challenge timeout). requestExtraParams still not producing extraParams response. 48 commits. Continuing investigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DocumentFragment.appendChild swallows errors (catches and returns child). Zero errors logged — appendChild doesn't throw, it just doesn't get called by Turnstile's Bt.appendChild(H). Origin fix confirmed working (zero wrong-origin drops). Widget is in map, handler runs, event string matches. But shadow.querySelector never called — the requestExtraParams case body doesn't execute despite all gates passing. The render function doesn't create shadow DOM structure (only hidden input). This confirms appendChild on DocumentFragment isn't called during render. 50 commits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
V8's prototype chain for ShadowRoot doesn't inherit Node methods through DocumentFragment. Added appendChild, insertBefore, removeChild directly to ShadowRoot.JsApi (in addition to DocumentFragment.JsApi). This ensures Bt.appendChild(H) in Turnstile's render goes through the Zig bridge, triggering iframeAddedCallback for shadow DOM iframes. 51 commits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hunterjsb
added a commit
that referenced
this pull request
Apr 4, 2026
Worker #1: 13 bytes (likely debugger check Worker — missing eval("debugger") message) Workers #2-5: 80 bytes each (PoW workers with onmessage=eval handler) Chrome creates Workers w2+w3 where w3 gets eval("debugger") check. Our VM skips the debugger check — Worker #1 blob may not set up onmessage correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hunterjsb
added a commit
that referenced
this pull request
Apr 4, 2026
…ssing Chrome Worker timeline (exact): - #1 t=57: "you"==="bot" (never receives messages) - #2 t=470: eval handler (all probes + timers) - #3 t=1116: eval handler (debugger check) Chrome sends multiple messages quickly, Workers process concurrently. Our Worker polyfill: main-thread sequential. Each response triggers next message. 10-15ms delivery delay helps but can't simulate concurrency. VM detects sequential processing (events arrive too slowly). Adjusted delay to 10-15ms (Chrome: 13-30ms round-trip). Still 1 chl_api_ni request — VM creates debugger Worker in Chrome but not in ours because timer test runs too late. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hunterjsb
pushed a commit
that referenced
this pull request
May 1, 2026
Adds limited support for window.open. This leverages the new page container and behaves similarly to an iframe. There are many things not implemented, but the most significant are: 1- target=window_name or target=_blank don't work (but this could be added pretty easily I think) 2- Windows (which are is just a Frame) are shutdown when the Page is shutdown. They would need to be owned by the Session (rather than the Page), but I'm not really confident in that right now. 3- No CDP testing. There are maybe CDP-specific messages we need to emit (or maybe messages that we shouldn't emit). As-is, this should help with common cases where: 1. window.open is called but doesn't matter (won't give a JS error) 2. window.open is short-lived, or, more specifically, lives only for the duration of the page that opened it (e.g. a login popup). 3. WPT tests! (because most of these fit in #2).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
window.__iframeDebug,window.__tsWrapped, andwindow.__tsREPdebug globals from the stealth injection script#tfragment hack on deferred iframe.src assignment🤖 Generated with Claude Code