a
    ¦’<j¼f  ã                   @   sD  d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZmZ ej ej e¡¡Zej ed¡Zej ed¡Zej ed¡Zdd„ ZG d	d
„ d
eƒZdd„ Zdd„ Zdd„ ZG dd„ dƒZG dd„ dƒZG dd„ dƒZdd„ Zdd„ Zdd„ Z dd„ Z!dd „ Z"d!d"„ Z#e$d#kr@e %e &e!ƒ ¡¡ dS )$uË  
hasfocus-always-true spike â€” 3-test validator

Tests document.hasFocus() behavior in three browser configs:
  1) vanilla headless Chromium (Playwright)        â€” baseline
  2) patchright headless Chromium (stealth fork)    â€” probe target
  3) vanilla headful Chromium via Xvfb (optional)   â€” headful cross-reference

The probe (probe.js) opens a popup, blurs the main window, and samples
document.hasFocus() / visibilityState / hidden at multiple checkpoints,
including inside a same-origin iframe.

Signal logic:
  STEALTH            = hasFocus=true at every checkpoint (always-true patch)
  HEADLESS (real)    = hasFocus=false once window is blurred (no OS focus)
  HEADFUL  (real)    = hasFocus=true only when tab/window is in front

PASS    = vanilla=BASELINE / BASELINE-HEADLESS and patchright shows DETECTED-STEALTH
MARGINAL = both browsers land in the same bucket (no differential signal)
REJECT  = control broken, popup blocked everywhere, or all errored
é    N)Ú
HTTPServerÚSimpleHTTPRequestHandlerúprobe.jszRESULTS.jsonzVALIDATION-REPORT.mdc                  C   s*   t   ¡ } |  d¡ |  ¡ d }|  ¡  |S )N)ú	127.0.0.1r   é   )ÚsocketÚbindÚgetsocknameÚclose)ÚsÚp© r   úK/home/huxley/projects/detect-bot/spikes/hasfocus-always-true/test_runner.pyÚ_find_free_port(   s
    
r   c                       s0   e Zd Z‡ fdd„Zdd„ Z‡ fdd„Z‡  ZS )Ú_Handlerc                    s   t ƒ j|dti|¤Ž d S )NZ	directory)ÚsuperÚ__init__Ú	SPIKE_DIR©ÚselfÚargsÚkwargs©Ú	__class__r   r   r   1   s    z_Handler.__init__c                 O   s   d S ©Nr   r   r   r   r   Úlog_message3   s    z_Handler.log_messagec                    s   t ƒ  |¡}| d¡rŒtj |¡rŒzVt|dddd}| d¡}W d   ƒ n1 sT0    Y  | ¡  	¡  
d¡rvW dS W n tyŠ   Y n0 |S )	Nz.jsÚrzutf-8Úignore)ÚencodingÚerrorsé@   )z	<!doctypez<htmlztext/html; charset=utf-8)r   Ú
guess_typeÚendswithÚosÚpathÚisfileÚopenÚreadÚlstripÚlowerÚ
startswithÚ	Exception)r   r$   ZmtÚfÚheadr   r   r   r!   5   s    (
z_Handler.guess_type)Ú__name__Ú
__module__Ú__qualname__r   r   r!   Ú__classcell__r   r   r   r   r   0   s   r   c                 C   s*   t d| ftƒ}tj|jdd}| ¡  |S )Nr   T)ÚtargetÚdaemon)r   r   Ú	threadingÚThreadÚserve_foreverÚstart)ÚportÚhttpdÚtr   r   r   Ústart_serverB   s    r;   c              
   Ã   sÚ   | j |dddI d H  z| jdddI d H  W n2 ty` } zdd|› iW  Y d }~S d }~0 0 z| jd	d
dI d H  W nN tyÈ } z6|  ¡ I d H d d… }dd|› d|› iW  Y d }~S d }~0 0 |  d¡I d H S )NÚdomcontentloadedé N  ©Z
wait_untilÚtimeoutz#startiˆ  ©r?   Úerrorzclick #start failed: úwindow.__dbReady === trueé˜:  éX  z__dbReady never set: ú; body[:600]=úwindow.__dbResult)ÚgotoÚclickr+   Úwait_for_functionÚcontentÚevaluate)ÚpageÚurlÚeÚbodyr   r   r   Ú	run_probeI   s    $*rP   c                 Ã   s˜  t d|› dƒ z$|ƒ }t |¡r.|I d H }n|}W n4 tyh } z|d|› dœW  Y d }~S d }~0 0 z| ¡ I d H }| ¡ I d H }|j|d dddI d H  z|jd	d
dI d H  W n| ty8 } zb| ¡ I d H d d… }	|d|› d|	› dœW  Y d }~W z| 	¡ I d H  W S  ty,   Y S 0 S d }~0 0 | 
d¡I d H }
| 	¡ I d H  W z| 	¡ I d H  W n ty€   Y n0 n*z| 	¡ I d H  W n tyª   Y n0 0 d|
v rÌt d|
d › ƒ nÂ|
 dd¡}|
 di ¡}t d|› ƒ t d| d¡› ƒ t d| d¡› d| d¡› ƒ t d| d¡› d| d¡› ƒ t d| d ¡› d| d!¡› ƒ t d"| d#¡› ƒ t d$| d%¡› ƒ ||
d&œS )'Nz
=== z ===zlaunch failed: ©ÚnamerA   ú?auto=1r<   r=   r>   rB   rC   r@   rD   ú__dbReady never set (auto): rE   rF   rA   ú	  ERROR: Úverdictú?Úsummaryú  verdict:                   ú  popupOpened:               ÚpopupOpenedú  initial.hasFocus/hidden:   ÚinitialHasFocusú / ÚinitialHiddenú  afterPopup.hasFocus/vs:    ÚafterPopupHasFocusÚafterPopupVisibilityStateú  afterBlur.hasFocus/hidden: ÚafterBlurHasFocusÚafterBlurHiddenú  rapid hasFocus all true:   ÚrapidAllTrueú  iframeAlwaysTrue:          ÚiframeAlwaysTrue©rR   Úresult)ÚprintÚasyncioÚiscoroutiner+   Únew_contextÚnew_pagerG   rI   rJ   r
   rK   Úget)r   rR   Zbrowser_factoryrM   ÚmaybeÚbrowserrN   ÚctxrL   rO   rk   Úvr   r   r   r   Úrun_one_browserW   sX    
&"ùý
   rv   c                   @   s$   e Zd Zdd„ Zdd„ Zdd„ ZdS )Ú_AsyncProxyc                 C   s   |\| _ | _d S r   )Ú_pÚ_browser)r   Zsync_handler   r   r   r   ƒ   s    z_AsyncProxy.__init__c                 Ã   s   | j  ¡ }t|| j | jƒS r   )ry   ro   Ú	_AsyncCtxrx   )r   rt   r   r   r   ro   …   s    
z_AsyncProxy.new_contextc                 Ã   sH   z| j  ¡  W n ty    Y n0 z| j ¡  W n tyB   Y n0 d S r   )ry   r
   r+   rx   Ústop©r   r   r   r   r
   ˆ   s    z_AsyncProxy.closeN)r.   r/   r0   r   ro   r
   r   r   r   r   rw   ‚   s   rw   c                   @   s$   e Zd Zdd„ Zdd„ Zdd„ ZdS )rz   c                 C   s   || _ || _|| _d S r   )Ú_ctxry   rx   )r   rt   rs   r   r   r   r   r   ”   s    z_AsyncCtx.__init__c                 Ã   s   t | j ¡ ƒS r   )Ú
_AsyncPager}   rp   r|   r   r   r   rp   ˜   s    z_AsyncCtx.new_pagec                 Ã   s&   z| j  ¡  W n ty    Y n0 d S r   )r}   r
   r+   r|   r   r   r   r
   š   s    z_AsyncCtx.closeN)r.   r/   r0   r   rp   r
   r   r   r   r   rz   “   s   rz   c                   @   s<   e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd	d
„ Zdd„ ZdS )r~   c                 C   s
   || _ d S r   )Ú_page)r   rL   r   r   r   r   ¢   s    z_AsyncPage.__init__c                 Ë   s   | j j|fi |¤ŽS r   )r   rG   )r   rM   Úkwr   r   r   rG   ¤   s    z_AsyncPage.gotoc                 Ë   s   | j j|fi |¤ŽS r   )r   rH   )r   Zselr€   r   r   r   rH   ¦   s    z_AsyncPage.clickc                 Ë   s   | j j|fi |¤ŽS r   )r   rI   )r   Úexprr€   r   r   r   rI   ¨   s    z_AsyncPage.wait_for_functionc                 Ã   s   | j  |¡S r   )r   rK   )r   r   r   r   r   rK   ª   s    z_AsyncPage.evaluatec                 Ã   s
   | j  ¡ S r   )r   rJ   r|   r   r   r   rJ   ¬   s    z_AsyncPage.contentN)	r.   r/   r0   r   rG   rH   rI   rK   rJ   r   r   r   r   r~   ¡   s   r~   c                 C   s  t dƒ d}zTddlm} |ƒ *}|jjdddgd}| ¡ }| ¡ }|j| d	 d
dd z|jddd W nb t	yÒ } zJ| 
¡ d d… }| ¡  |d|› d|› dœW  Y d }~W  d   ƒ W S d }~0 0 | d¡}	|	d u r8| d¡}
| 
¡ d d… }| ¡  |d|
d d… › d|› dœW  d   ƒ W S | ¡  W d   ƒ n1 sV0    Y  W n6 t	y˜ } z|d|› dœW  Y d }~S d }~0 0 t|	tƒrÄd|	v rÄt d|	d › ƒ nÂ|	 dd¡}|	 di ¡}t d |› ƒ t d!| d"¡› ƒ t d#| d$¡› d%| d&¡› ƒ t d'| d(¡› d%| d)¡› ƒ t d*| d+¡› d%| d,¡› ƒ t d-| d.¡› ƒ t d/| d0¡› ƒ ||	d1œS )2Nz
=== patchright-headless ===zpatchright-headlessr   )Úsync_playwrightTú--no-sandboxú--disable-dev-shm-usage©Zheadlessr   rS   r<   r=   r>   rB   rC   r@   rD   rT   rE   rQ   rF   z>Array.isArray(window.__dbLog) ? window.__dbLog.join('\n') : ''i   z.__dbResult was None after __dbReady=true; log=i  z; body[:800]=zpatchright launch/run failed: rA   rU   rV   rW   rX   rY   rZ   r[   r\   r]   r^   r_   r`   ra   rb   rc   rd   re   rf   rg   rh   ri   rj   )rl   Zpatchright.sync_apir‚   ÚchromiumÚlaunchro   rp   rG   rI   r+   rJ   r
   rK   Ú
isinstanceÚdictrq   )rM   rR   r‚   r   rs   rt   rL   rN   rO   rk   Úlogru   r   r   r   r   Ú_run_patchright_in_thread°   sN    
þ<


.,&   r‹   c                   C   s   t  d¡d uot  d¡d uS )NZXvfbúxvfb-run)ÚshutilÚwhichr   r   r   r   Ú
_have_xvfbÞ   s    r   c           	   
   C   s,  t dƒ d}tƒ s|ddœS d}z$tjdddd	d
|| gdddd}W n4 tyx } z|d|› dœW  Y d}~S d}~0 0 |jdkr¦|d|j› d|jdd… › dœS d}||jvrÎ|d|jdd… › dœS z t 	|j 
|d¡d  ¡ ¡}W nF ty4 } z,|d|› d|jdd… › dœW  Y d}~S d}~0 0 t|tƒr`d|v r`t d|d › ƒ nÂ| dd¡}| di ¡}t d|› ƒ t d| d ¡› ƒ t d!| d"¡› d#| d$¡› ƒ t d%| d&¡› d#| d'¡› ƒ t d(| d)¡› d#| d*¡› ƒ t d+| d,¡› ƒ t d-| d.¡› ƒ ||d/œS )0z=Run the probe in a real (visible-window) Chromium under Xvfb.z!
=== vanilla-headful-via-xvfb ===úvanilla-headful-via-xvfbzXvfb not available on PATHrQ   aë  
import json, sys
from playwright.sync_api import sync_playwright

url = sys.argv[1]
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, args=["--no-sandbox", "--disable-dev-shm-usage"])
    ctx = browser.new_context()
    page = ctx.new_page()
    page.goto(url + "?auto=1", wait_until="domcontentloaded", timeout=20000)
    page.wait_for_function("window.__dbReady === true", timeout=15000)
    result = page.evaluate("window.__dbResult")
    if result is None:
        log = page.evaluate("Array.isArray(window.__dbLog) ? window.__dbLog.join('\\n') : ''")
        browser.close()
        raise RuntimeError(f"__dbResult was None; log={log[:400]}")
    browser.close()
print("___HEADFUL_RESULT___" + json.dumps(result))
rŒ   z-az#--server-args=-screen 0 1280x800x24z/usr/bin/python3z-cTéx   )Úcapture_outputÚtextr?   zxvfb-run launch failed: Nr   zxvfb-run exit=z stderr=ipþÿÿZ___HEADFUL_RESULT___zno result marker; stdout=r   zjson parse failed: z; raw=rA   rU   rV   rW   rX   rY   rZ   r[   r\   r]   r^   r_   r`   ra   rb   rc   rd   re   rf   rg   rh   ri   rj   )rl   r   Ú
subprocessÚrunr+   Ú
returncodeÚstderrÚstdoutÚjsonÚloadsÚsplitÚstriprˆ   r‰   rq   )	rM   rR   ÚinnerÚprocrN   Zmarkerrk   ru   r   r   r   r   Ú_run_headful_via_xvfbâ   sF    
ÿý
&
"
 6   rŸ   c               
   ƒ   s  t dƒ t dƒ t dƒ t dt› ƒ t dt› ƒ tƒ } t| ƒ}d| › d‰t dˆ› ƒ ‡ fdd	„}d
dlm} d }zR|ƒ 4 I d H š*‰ tˆ d|ˆƒI d H }W d   ƒI d H  qÈ1 I d H s¾0    Y  W n4 tyþ } zdd|› dœ}W Y d }~n
d }~0 0 t	 
¡  d ‡fdd„¡I d H }tj dd¡dkr>dddœ}n:tƒ sZdddœ}t dƒ nt	 
¡  d ‡fdd„¡I d H }z| ¡  W n ty˜   Y n0 dt d¡d|||gdœ}ttdƒ }	tj||	dd W d   ƒ n1 sæ0    Y  t d t› ƒ | d!¡pi  d"d#¡}
| d!¡p$i  d"d#¡}| d!¡p<i  d"d#¡}| d!¡pTi  d$i ¡}| d!¡pli  d$i ¡}| d!¡p„i  d$i ¡}t|
||||||||ƒ	\}}t ƒ  t dƒ t d%|› ƒ t d&|› ƒ t dƒ t|||ƒ t d't› ƒ |d(krd
S |d)krdS d*S )+NzF======================================================================u/   HASFOCUS-ALWAYS-TRUE SPIKE â€” 3-test validatorzProbe: zWorking dir: zhttp://127.0.0.1:z	/probe.jszLocal server: c                      s   ˆ j jdg d¢dS )NT)rƒ   z--disable-setuid-sandboxr„   r…   )r†   r‡   r   )r   r   r   Úvanilla_headless,  s    þzmain.<locals>.vanilla_headlessr   )Úasync_playwrightzvanilla-headlesszplaywright crashed: rQ   c                      s   t ˆ ƒS r   )r‹   r   ©Ú	probe_urlr   r   Ú<lambda>=  ó    zmain.<locals>.<lambda>ZHF_SKIP_HEADFULÚ0Ú1r   zskipped via HF_SKIP_HEADFUL=1zXvfb/xvfb-run not on PATHz3
=== vanilla-headful-via-xvfb === SKIPPED (no Xvfb)c                      s   t ˆ ƒS r   )rŸ   r   r¢   r   r   r¤   H  r¥   zhasfocus-always-truez%Y-%m-%d %H:%M:%Sr   )ZspikeÚranAtZprobeÚtestsÚwé   )Úindentz
Wrote rk   rV   ÚERRORrX   zOVERALL VERDICT: zREASON: zWrote ÚPASSÚREJECTr   )rl   Ú
PROBE_PATHr   r   r;   Zplaywright.async_apir¡   rv   r+   rm   Úget_event_loopÚrun_in_executorr#   Úenvironrq   r   ÚshutdownÚtimeÚstrftimer&   ÚRESULTS_PATHr™   ÚdumpÚ_decideÚ_write_reportÚREPORT_PATH)r8   r9   r    r¡   Úr1rN   Úr2Úr3Úpayloadr,   Úv1Úv2Úv3Ús1Ús2Ús3ÚoverallÚreasonr   )r   r£   r   Úmain  sv    B&ÿ


ÿ
ü0	

rÈ   c	                 C   sf  d| v pd|v }	d|v pd|v }
d|v p.d|v }|	rN|
rNdd| › d|› dfS |	r`dd| › fS t dd	„ |||fD ƒƒ}|d
kr„dS |  d¡}| d¡p | d¡}|p®| d¡}|rà|rà|ràdd| › d|› d|rÔdn|› dfS | d¡r|rdd| › d|› dfS | d¡r*|s*dd| › dfS |sL| d¡rLdd|› dfS dd| › d|› d|› fS )Nr­   rA   r¯   z-both vanilla and patchright errored (vanilla=z, patchright=ú)zvanilla headless errored: c                 s   s.   | ]&}t | d ¡pi  di ¡ d¡ƒV  qdS )rk   rX   r[   N)Úboolrq   )Ú.0r:   r   r   r   Ú	<genexpr>„  s   ÿz_decide.<locals>.<genexpr>r   )r¯   uB   popup was blocked in every browser â€” focus-shift test cannot runZBASELINEzDETECTED-STEALTHzSUSPECTED-STEALTHr®   zvanilla=BASELINE (z$), patchright shows stealth signal (z), headful=ZOKu"    â€” differential signal confirmedÚMARGINALz>both vanilla and patchright land in BASELINE buckets (vanilla=u”   ) â€” no differential signal; either patchright's build does not patch hasFocus, or the patch is conditional on something this probe doesn't triggerz-vanilla already shows STEALTH-like behavior (uF   ) â€” control is contaminated; can't isolate patchright's contributionZDETECTEDz3headful via Xvfb also shows STEALTH-like behavior (uF   ) â€” the signal may be an Xvfb/headless artifact, not a stealth patchz(unexpected verdict combination: vanilla=z
, headful=)Úsumr*   )rÀ   rÁ   rÂ   rÃ   rÄ   rÅ   r¼   r½   r¾   Zv1_errZv2_errZv3_errZpopups_openedZvanilla_is_baselineZstealth_signalZheadful_cleanr   r   r   r¹   {  s@    ÿ

ÿ
ÿÿ
ÿr¹   c                 C   s*  | d }g }|  d¡ |  d¡ |  d| d › ¡ |  d|› d¡ |  d|› ¡ |  d¡ |  d	¡ |  d¡ |  d
¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d¡ |  d ¡ |  d¡ |  d!¡ |  d"¡ |  d#¡ |  d$¡ |  d%¡ |  d&¡ |  d'¡ |  d(¡ |  d)¡ |  d¡ |  d*¡ |  d¡ |D ]ì}|  d+|d, › d-¡ |  d¡ d.|v rz|  d/|d. › d-¡ |  d¡ q&|d0 }| d1i ¡}| d2d3¡}|  d4|› ¡ |  d5| d6d3¡d d7… › ¡ |  d8| d9¡› ¡ |  d:| d;¡› d<| d=¡› d>| d?¡› ¡ |  d@| dA¡› d<| dB¡› d>| dC¡› ¡ |  dD| dE¡› d<| dF¡› d>| dG¡› ¡ |  dH| dI¡› dJtdKdL„ | dMg ¡D ƒƒ› dNtdOdL„ | dMg ¡D ƒƒ› dP¡ |  dQ| dR¡› ¡ |  dS| dT¡› ¡ |  dU| dVd¡d dW… › d-¡ |  d¡ q&|  dX¡ |  d¡ |  dY¡ |  dZ¡ |D ]ô}d.|v rTqB|d0 }| d1i ¡}| d[g ¡}	|	D ]º}
|
 d\i ¡pŒi }|
 d]i ¡pži }|
 d^i ¡p°i }|  d_|d, › d`|
 dad¡› db| dcd3¡› dd| ded3¡› db| dfd3¡› dg|r| dcd3¡ndh› dg|r$| dcd3¡ndh› di¡ qxqB|  d¡ |  dj¡ |  d¡ |dkkrÆ|  dl¡ |  dm¡ |  dn¡ |  do¡ |  dp¡ |  dq¡ |  dr¡ |  ds¡ |  dt¡ |  du¡ nÊ|dvkrJ|  dw¡ |  dx¡ |  dy¡ |  dz¡ |  d{¡ |  d|¡ |  d}¡ |  d~¡ |  d¡ |  d€¡ |  d¡ |  d‚¡ nF|  dƒ¡ |  d„¡ |  d…¡ |  d†¡ |  d‡¡ |  dˆ¡ |  d‰¡ |  d¡ |  dŠ¡ |  d¡ |  d‹¡ |  dŒ¡ |  d¡ |  dŽ¡ |  d¡ |  d¡ ttdƒ }| d‘ |¡¡ W d   ƒ n1 s0    Y  d S )’Nr©   u,   # hasfocus-always-true â€” VALIDATION REPORTÚ z**Generated:** r¨   z**Overall verdict:** **z**z**Reason:** z## HypothesiszMPer `NIGHTLY-FINDINGS-2026-06-23`, stealth browser frameworks as of June 2026zMnow ship with `document.hasFocus()` patched to return `true` unconditionally,zNso the page LOOKS focused even when running headless without a window manager.zPReal headless Chromium (no WM) should report `false` once the window is blurred.zQReal headful Chromium should report `true` only when the tab/window has OS focus.zOThe signal inverts by context: a `STEALTH` framework that always returns `true`uQ   makes the page appear focused when it shouldn't â€” a false positive in the OTHERzKdirection (a real headless browser correctly reports `false` after a blur).z	## MethodzIThe probe (`probe.js`) samples `document.hasFocus()` at four checkpoints:u9   1. **T1 initial** â€” page just loaded, should be focuseduJ   2. **T2 after popup** â€” a `window.open()` popup stole OS focus from mainuJ   3. **T3 after `window.blur()`** â€” main window explicitly blurred, then az<   `popup.focus()` / `popup.blur()` / `window.focus()` cycleuU   4. **T4 rapid x10** â€” 10 samples at 20 ms intervals (catches time-of-check patches)z,At each checkpoint it captures three values:u.   - `document.hasFocus()` â€” the primary signaluS   - `document.visibilityState` â€” cross-reference; should be `'hidden'` when blurredu/   - `document.hidden` â€” cross-reference booleanzNIt also probes the same values inside a same-origin `srcdoc` iframe (differentuP   `WindowProxy` / `Document` per the HTML spec) â€” stealth frameworks often patchzIthe top-level Document only, leaking the original behavior in the iframe.z## Verdict logicuV   - **STEALTH** â€” `hasFocus` returns `true` at every checkpoint (initial, after popup,zO  after blur, rapid x10). Optionally paired with `document.hidden=true` at somez2  checkpoint (inconsistent with any real browser).uQ   - **BASELINE** â€” `hasFocus` correctly toggles: `true` at initial, `false` afterz@  blur()/popup steal. Consistent with real Chromium focus state.uN   - **SUSPECTED-HEADLESS-NO-WINDOW** â€” `hasFocus=false` everywhere. ConsistentzK  with real headless without a window manager (no OS focus ever available).uT   - **BASELINE-HEADLESS** â€” `hasFocus=false` initially, mixed during rapid sampling.zE  Consistent with real headless where focus is unstable without a WM.z## Testsz### `rR   ú`rA   z**ERROR:** `rk   rX   rV   rW   z- **verdict:** z- **userAgent:** Z	userAgentr‘   z- **popup opened:** r[   z- **initial:** hasFocus=r]   z, visibilityState=`ZinitialVisibilityStatez
`, hidden=r_   z- **after popup:** hasFocus=ra   rb   ZafterPopupHiddenz- **after blur():** hasFocus=rd   ZafterBlurVisibilityStatere   z- **rapid x10 all true:** rg   z  (true=c                 s   s   | ]}|rd V  qdS ©r   Nr   ©rË   Úxr   r   r   rÌ   è  r¥   z _write_report.<locals>.<genexpr>Zrapidz, false=c                 s   s   | ]}|sd V  qdS rÑ   r   rÒ   r   r   r   rÌ   é  r¥   rÉ   z- **iframe (srcdoc) probed:** ZiframeSameOriginz- **iframe always true:** ri   z- **`hasFocus.toString()`:** `ZhasFocusStringéP   z## Per-row detailza| Stage | main.hasFocus | main.visibilityState | main.hidden | iframe.hasFocus | popup.hasFocus |z|---|---|---|---|---|---|ÚrowsrÈ   ÚiframeÚpopupz| `r^   Zstagez` | ZhasFocusz | `ZvisibilityStateZhiddenz | zn/az |z## Interpretationr®   zK- **Vanilla headless Chromium** behaves as expected: `hasFocus()` is `true`zI  at page load and flips to `false` after the popup steals focus or afterz(  `window.blur()`. The control is clean.zE- **patchright headless Chromium** returns `hasFocus()=true` at everyzG  checkpoint (initial, after popup, after blur, rapid x10). This is thezV  `hasfocus-always-true` stealth signature predicted by `NIGHTLY-FINDINGS-2026-06-23`.zG- A detector can confidently flag a browser as a stealth framework whenzK  `document.hasFocus()` is `true` after a `window.blur()` + popup cycle ANDzL  `document.visibilityState === 'hidden'` (the latter is the cross-referencezJ  that proves the window actually lost focus but `hasFocus()` still lies).rÍ   uI   - Vanilla headless and patchright produced the same verdict bucket â€” noz&  differential signal from this build.z- Possible causes:zK  1. patchright's build does not patch `document.hasFocus` in this version.zH  2. The patch is conditional (e.g. only when running in a real headlessuI        context, not when launched via patchright's own driver â€” which mayz      already disable the patch).zE  3. The probe's blur mechanic didn't actually blur in either browserzJ     (e.g. Playwright's headless mode retains focus regardless of blur()).zC- Recommended next step: re-run with a known-stealth framework thatzG  explicitly injects `Object.defineProperty(document, 'hasFocus', ...)`zJ  returning `() => true`, and confirm the probe flips to DETECTED-STEALTH.zD- The control or the probe itself failed. See per-test errors above.z- Common causes:zP  - Popups blocked (Chromium `--headless=new` blocks `window.open` by default inuQ       some configs â€” try `--disable-features=BlockInsecurePrivateNetworkRequests`z6    or pass `--headless` instead of `--headless=new`).z*  - Playwright/patchright driver mismatch.z3  - Xvfb headful path missing required system libs.z## Verdict keyuP   - **PASS** â€” vanilla=BASELINE / BASELINE-HEADLESS, patchright=DETECTED-STEALTHzS  or SUSPECTED-STEALTH, headful (if run) = BASELINE. Confirmed differential signal.uT   - **MARGINAL** â€” both headless runs land in the same bucket, or headful disagrees.z/  Re-test with a known leaky stealth framework.uQ   - **REJECT** â€” control broken (popup blocked everywhere, or both runs errored).rª   Ú
)Úappendrq   rÎ   r&   r»   ÚwriteÚjoin)r¿   rÆ   rÇ   r©   Úlinesr:   r   r   ru   rÕ   ÚrowrÈ   rÖ   r×   r,   r   r   r   rº   ž  s@   



















































 ÿÿÿÿÿÿÿþ
"





ÿ
þ
ýüû








































rº   Ú__main__)'Ú__doc__rm   r™   r#   r   r   r”   Úsysr4   rµ   Zhttp.serverr   r   r$   ÚdirnameÚabspathÚ__file__r   rÛ   r°   r·   r»   r   r   r;   rP   rv   rw   rz   r~   r‹   r   rŸ   rÈ   r¹   rº   r.   Úexitr•   r   r   r   r   Ú<module>   s>   +.=\# 
