Ë
   ò-Mdõrã                  ó  — d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ ddlmZmZmZmZmZmZmZmZ ddlmZ dddd	œ	 	 	 	 	 	 	 dd
„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Z dd„Z!dd„Z"dd„Z#dd„Z$dd„Z%y)z4Tests for the explicit remote-model egress boundary.é    )ÚannotationsN)ÚSQLiteDatabase)ÚAuditEventTypeÚAuditLogÚ
AuditQueryÚDataClassificationÚ
EgressGateÚEgressPolicyÚEgressRequestÚSecurityError)ÚEgressPermissionGateúapproved-local-proxyÚ ©ÚclassificationsÚprovider_idÚdestinationc                óœ   — t        d|t        j                  d«      j                  «       | xs t	        t
        j                  h«      |¬«      S )Nzknowledge-agents   synthetic payload)Úagent_idr   Úpayload_sha256r   r   )r   ÚhashlibÚsha256Ú	hexdigestÚ	frozensetr   ÚPUBLICr   s      ú8/home/oem/CyberBrain26-unified/tests/test_egress_gate.pyÚrequestr      sG   € ô Ø"ØÜ—~‘~Ð&:Ó;×EÑEÓGØ'ÒQ¬9Ô6H×6OÑ6OÐ5PÓ+QØôð ó    c                 óÀ   — t        t        «       d¬«      } t        j                  t        d¬«      5  | j                  t        «       «       d d d «       y # 1 sw Y   y xY w)NT©Ú	audit_logÚbase_decisionÚ	allowlist©Úmatch©r	   r   ÚpytestÚraisesr   Ú	authorizer   ©Úgates    r   ÚCtest_remote_egress_is_denied_without_an_explicit_provider_allowlistr,   &   sA   € Ü¤£
¸$Ô?€Dä	�‰”}¨KÔ	8ñ "Ø�‰”w“yÔ!÷"÷ "ñ "úó   ±AÁAc                 ó”  — t        t        dh«      ¬«      } t        | t        «       d¬«      }t	        j
                  t        «      5  t        | «       d d d «       t	        j
                  t        d¬«      5  |j                  t        t        t        j                  h«      ¬«      «       d d d «       y # 1 sw Y   ŒaxY w# 1 sw Y   y xY w)Nr   ©Úallowed_remote_providersTr    r   r$   )r   )r
   r   r	   r   r'   r(   Ú	TypeErrorr   r)   r   r   ÚPERSONAL)ÚpolicyÚgate_with_audits     r   ÚFtest_remote_egress_requires_audit_and_refuses_personal_data_by_defaultr5   -   s¢   € Ü´9Ð>TÐ=UÓ3VÔW€FÜ  ´8³:ÈTÔR€Oô 
�‰”yÓ	!ñ Ü�6Ô÷ô 
�‰”}Ð,=Ô	>ñ eØ×!Ñ!¤'¼)ÔEW×E`ÑE`ÐDaÓ:bÔ"cÔd÷eð e÷ð ú÷eð eús   ÁB2Á54B>Â2B;Â>Cc                óÎ  — t        t        | dz  «      ¬«      }t        t        dh«      t        t        j
                  h«      ¬«      }t        ||d¬«      j                  t        «       «       |j                  t        t        j                  ¬«      «      d   }|j                  }d	}||k(  }|s­t        j                  d
|fd||f«      dt!        j"                  «       v st        j$                  |«      rt        j&                  |«      ndt        j&                  |«      t        j&                  |«      dœz  }dd|iz  }t)        t        j*                  |«      «      ‚d x}x}}|j,                  }d}||k(  }|s­t        j                  d
|fd||f«      dt!        j"                  «       v st        j$                  |«      rt        j&                  |«      ndt        j&                  |«      t        j&                  |«      dœz  }dd|iz  }t)        t        j*                  |«      «      ‚d x}x}}|j.                  d   }	dg}|	|k(  }
|
slt        j                  d
|
fd|	|f«      t        j&                  |	«      t        j&                  |«      dœz  }dd|iz  }t)        t        j*                  |«      «      ‚d x}	x}
}d}	|j.                  }t1        |«      }|	|v}
|
�s
t        j                  d|
fd|	|f«      t        j&                  |	«      dt!        j"                  «       v st        j$                  t0        «      rt        j&                  t0        «      nddt!        j"                  «       v st        j$                  |«      rt        j&                  |«      ndt        j&                  |«      t        j&                  |«      dœz  }dd|iz  }t)        t        j*                  |«      «      ‚d x}	x}
x}}y )Núegress.sqlite3©Údatabaser   ©r0   Úallowed_remote_classificationsTr    ©Ú
event_typer   Úmodel_egress©ú==)z.%(py2)s
{%(py2)s = %(py0)s.action
} == %(py5)sÚevent©Úpy0Úpy2Úpy5úassert %(py7)sÚpy7Úallowed©z.%(py2)s
{%(py2)s = %(py0)s.result
} == %(py5)sr   Úpublic)z%(py1)s == %(py4)s)Úpy1Úpy4zassert %(py6)sÚpy6zsynthetic payload©únot in)zR%(py1)s not in %(py8)s
{%(py8)s = %(py3)s(%(py6)s
{%(py6)s = %(py4)s.metadata
})
}Ústr)rK   Úpy3rL   rM   Úpy8zassert %(py10)sÚpy10)r   r   r
   r   r   r   r	   r)   r   Úqueryr   r   ÚDATA_ACCESSÚactionÚ
@pytest_arÚ_call_reprcompareÚ@py_builtinsÚlocalsÚ_should_repr_global_nameÚ	_safereprÚAssertionErrorÚ_format_explanationÚresultÚmetadatarP   )Útmp_pathÚauditr3   rA   Ú@py_assert1Ú@py_assert4Ú@py_assert3Ú@py_format6Ú@py_format8Ú@py_assert0Ú@py_assert2Ú@py_format5Ú@py_format7Ú@py_assert5Ú@py_assert7Ú@py_format9Ú@py_format11s                    r   ÚCtest_allowed_remote_egress_records_only_a_digest_and_classificationrp   :   sR  € Üœn¨XÐ8HÑ-HÓIÔJ€EÜÜ!*Ð,BÐ+CÓ!DÜ'0Ô2D×2KÑ2KÐ1LÓ'Mô€Fô
 ˆv °dÔ;×EÑEÄgÃiÔPà�K‰Kœ
¬n×.HÑ.HÔIÓJÈ1ÑM€EØ�<‰<Ð)˜>Ð)ˆ<˜>Ñ)×)×)Õ)ˆ<˜>×)×)×)×)×)Ñ)ˆ5×)×)Ó)ˆ5×)×)Ô)ˆ<×)×)Ò)˜>×)×)×)×)×)×)Ó)Ø�<‰<Ð$˜9Ð$ˆ<˜9Ñ$×$×$Õ$ˆ<˜9×$×$×$×$×$Ñ$ˆ5×$×$Ó$ˆ5×$×$Ô$ˆ<×$×$Ò$˜9×$×$×$×$×$×$Ó$Ø�>‰>Ð+Ñ,Ð:°°
Ð:Ð,°
Ñ:×:×:Õ:Ð,°
×:×:Ó:Ð,×:×:Ò:°
×:×:×:×:×:×:Ó:ØÐ9¨%¯.©.Ð9¤c¨.Ó&9Ð9ÐÐ&9Ð9×9×9Ö9ÐÐ&9×9×9Ó9Ð×9×9×9×9×9Ð9¤c×9×9Ó9¤c×9×9×9×9×9Ò9¨%×9×9Ó9¨%×9×9Ô9¨.×9×9Ò9Ð&9×9×9×9×9×9×9Ö9r   c                 óÀ   — t        t        «       d¬«      } t        j                  t        d¬«      5  | j                  t        «       «       ddd«       y# 1 sw Y   yxY w)zANegative test: no default-open path exists through this boundary.Tr    r#   r$   Nr&   r*   s    r   Ú7test_provider_in_neither_allowlist_is_denied_by_defaultrr   J   sA   € ä¤£
¸$Ô?€Dä	�‰”}¨KÔ	8ñ "Ø�‰”w“yÔ!÷"÷ "ñ "úr-   c                ó>  — t        t        | dz  «      ¬«      }t        t        «       t        dh«      ¬«      }t	        ||d¬«      }|j                  t        «       «       |j                  t        t        j                  ¬«      «      d   }|j                  }d	}||k(  }|s­t        j                  d
|fd||f«      dt        j                  «       v st        j                   |«      rt        j"                  |«      ndt        j"                  |«      t        j"                  |«      dœz  }dd|iz  }	t%        t        j&                  |	«      «      ‚dx}x}}t)        j*                  t,        d¬«      5  |j                  t        d¬«      «       ddd«       y# 1 sw Y   yxY w)zFA provider listed as local must not slip past the classification gate.r7   r8   r   ©r0   Úlocal_provider_idsTr    r<   r   Úlocalr?   ©z6%(py2)s
{%(py2)s = %(py0)s.network_action
} == %(py5)srA   rB   rF   rG   Nr#   r$   znot-listed-anywhere)r   )r   r   r
   r   r	   r)   r   rT   r   r   rU   Únetwork_actionrW   rX   rY   rZ   r[   r\   r]   r^   r'   r(   r   ©
ra   rb   r3   r+   rA   rc   rd   re   rf   rg   s
             r   Ú1test_local_allowlist_does_not_grant_remote_egressrz   R   s  € äœn¨XÐ8HÑ-HÓIÔJ€EÜÜ!*£Ü$Ð&<Ð%=Ó>ô€Fô �f¨¸TÔB€Dð 	‡N�N”7“9Ôà�K‰Kœ
¬n×.HÑ.HÔIÓJÈ1ÑM€EØ×ÑÐ* 7Ð*Ð 7Ñ*×*×*Õ*Ð 7×*×*×*×*×*Ñ*ˆ5×*×*Ó*ˆ5×*×*Ô*Ð×*×*Ò* 7×*×*×*×*×*×*Ó*ô 
�‰”}¨KÔ	8ñ CØ�‰”wÐ+@ÔAÔB÷C÷ Cñ Cús   Å.FÆFc                 ó6  — t        t        «       t        dh«      ¬«      } t        | t        «       d¬«      }t	        j
                  t        d¬«      5  |j                  t        t        t        j                  h«      d¬«      «       d	d	d	«       y	# 1 sw Y   y	xY w)
zþNegative test: a local provider is not a licence to skip the gate.

    Naming a provider "local" only proves the owner listed it.  If that were
    enough to skip classification, the allowlist would be an exfiltration
    bypass under another name.
    r   rt   Tr    r   r$   zhttp://localhost:11434/v1©r   r   N)r
   r   r	   r   r'   r(   r   r)   r   r   Ú
RESTRICTED©r3   r+   s     r   Ú6test_local_provider_still_passes_a_classification_gater   f   s€   € ô Ü!*£Ü$Ð&<Ð%=Ó>ô€Fô �f¬«
À$ÔG€Dô 
�‰”}Ð,=Ô	>ñ 
Ø�‰ÜÜ )Ô+=×+HÑ+HÐ*IÓ JØ7ôô	
÷
÷ 
ñ 
ús   Á5BÂBc                 ó  — t        t        «       t        dh«      ¬«      } t        | t        «       d¬«      }t	        j
                  t        d¬«      5  |j                  t        d¬«      «       d	d	d	«       y	# 1 sw Y   y	xY w)
zENegative test: "local" is proven by the destination, not by the name.r   rt   Tr    Úloopbackr$   zhttps://models.example.com/v1©r   N)	r
   r   r	   r   r'   r(   r   r)   r   r~   s     r   Ú;test_local_provider_cannot_claim_a_non_loopback_destinationrƒ   }   sh   € äÜ!*£Ü$Ð&<Ð%=Ó>ô€Fô �f¬«
À$ÔG€Dä	�‰”}¨JÔ	7ñ MØ�‰”wÐ+JÔKÔL÷M÷ Mñ Mús   ÁA6Á6A?c                ó\  — t        t        | dz  «      ¬«      }t        t        dh«      ¬«      }t	        ||d¬«      }|j                  t        t        t        j                  h«      d¬«      «       |j                  t        t        j                  ¬	«      «      d
   }|j                  }d}||k(  }|s­t        j                  d|fd||f«      dt!        j"                  «       v st        j$                  |«      rt        j&                  |«      ndt        j&                  |«      t        j&                  |«      dœz  }dd|iz  }	t)        t        j*                  |	«      «      ‚d x}x}}|j,                  }d}||k(  }|s­t        j                  d|fd||f«      dt!        j"                  «       v st        j$                  |«      rt        j&                  |«      ndt        j&                  |«      t        j&                  |«      dœz  }dd|iz  }	t)        t        j*                  |	«      «      ‚d x}x}}y )Nr7   r8   r   ©ru   Tr    zhttp://127.0.0.1:11434/v1r|   r<   r   rv   r?   rw   rA   rB   rF   rG   rH   rI   )r   r   r
   r   r	   r)   r   r   r2   rT   r   r   rU   rx   rW   rX   rY   rZ   r[   r\   r]   r^   r_   ry   s
             r   Ú5test_local_egress_to_loopback_is_allowed_and_recordedr†   ‰   s`  € Üœn¨XÐ8HÑ-HÓIÔJ€EÜ¬YÐ8NÐ7OÓ-PÔQ€FÜ�f¨¸TÔB€Dà‡N�NÜÜ%Ô'9×'BÑ'BÐ&CÓDØ3ô	
ôð �K‰Kœ
¬n×.HÑ.HÔIÓJÈ1ÑM€EØ×ÑÐ* 7Ð*Ð 7Ñ*×*×*Õ*Ð 7×*×*×*×*×*Ñ*ˆ5×*×*Ó*ˆ5×*×*Ô*Ð×*×*Ò* 7×*×*×*×*×*×*Ó*Ø�<‰<Ð$˜9Ð$ˆ<˜9Ñ$×$×$Õ$ˆ<˜9×$×$×$×$×$Ñ$ˆ5×$×$Ó$ˆ5×$×$Ô$ˆ<×$×$Ò$˜9×$×$×$×$×$×$Ô$r   c                óT  — t        t        | dz  «      ¬«      }t        t        dh«      ¬«      }t	        ||d¬«      j                  t        d¬«      «       t        |j                  t        t        j                  ¬	«      «      d
   j                  «      }d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v }|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}y)z4Negative test: secrets must not reach the audit row.r7   r8   r   r/   Tr    zGhttps://user:supersecret@provider.example:443/v1/chat?api_key=TOPSECRETr‚   r<   r   ÚsupersecretrN   ©z%(py1)s not in %(py3)sr`   ©rK   rQ   úassert %(py5)srE   NÚ	TOPSECRETzuser:zprovider.example:443©Úin©z%(py1)s in %(py3)s©r   r   r
   r   r	   r)   r   rP   rT   r   r   rU   r`   rW   rX   r\   rY   rZ   r[   r]   r^   ©ra   rb   r3   r`   rh   ri   Ú@py_format4rf   s           r   Ú?test_destination_sanitiser_strips_credentials_and_query_secretsr“   š   sÝ  € äœn¨XÐ8HÑ-HÓIÔJ€EÜ´9Ð>TÐ=UÓ3VÔW€Fäˆv °dÔ;×EÑEÜÐeÔfôô �5—;‘;œz´^×5OÑ5OÔPÓQÐRSÑT×]Ñ]Ó^€HØÐ(ˆ= Ð(×(×(Õ(ˆ= ×(×(Ó(ˆ=×(×(×(×(×(Ð( ×(×(Ó( ×(×(×(×(×(×(Ó(ØÐ&ˆ;˜hÐ&×&×&Õ&ˆ;˜h×&×&Ó&ˆ;×&×&×&×&×&Ð&˜h×&×&Ó&˜h×&×&×&×&×&×&Ó&ØÐ"ˆ7˜(Ð"×"×"Õ"ˆ7˜(×"×"Ó"ˆ7×"×"×"×"×"Ð"˜(×"×"Ó"˜(×"×"×"×"×"×"Ó"Ø!Ð-Ð! XÐ-×-×-Õ-Ð! X×-×-Ó-Ð!×-×-×-×-×-Ð- X×-×-Ó- X×-×-×-×-×-×-Ô-r   c                óT  — t        t        | dz  «      ¬«      }t        t        dh«      ¬«      }t	        ||d¬«      j                  t        d¬«      «       t        |j                  t        t        j                  ¬	«      «      d
   j                  «      }d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v}|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}d}||v }|s™t        j                  d|fd||f«      t        j                  |«      dt!        j"                  «       v st        j$                  |«      rt        j                  |«      nddœz  }dd|iz  }t'        t        j(                  |«      «      ‚dx}}y)z@Negative test: percent-encoded userinfo must not survive either.r7   r8   r   r/   Tr    z,https://user%40evil:pass@provider.example/v1r‚   r<   r   ÚpassrN   r‰   r`   rŠ   r‹   rE   Nz%40Úevilzprovider.exampler�   r�   r�   r‘   s           r   Ú>test_destination_sanitiser_survives_percent_encoded_separatorsr—   ª   sÝ  € äœn¨XÐ8HÑ-HÓIÔJ€EÜ´9Ð>TÐ=UÓ3VÔW€Fäˆv °dÔ;×EÑEÜÐJÔKôô �5—;‘;œz´^×5OÑ5OÔPÓQÐRSÑT×]Ñ]Ó^€HØÐ!ˆ6˜Ð!×!×!Õ!ˆ6˜×!×!Ó!ˆ6×!×!×!×!×!Ð!˜×!×!Ó!˜×!×!×!×!×!×!Ó!ØÐ ˆ5˜Ð × × Õ ˆ5˜× × Ó ˆ5× × × × × Ð ˜× × Ó ˜× × × × × × Ó ØÐ!ˆ6˜Ð!×!×!Õ!ˆ6˜×!×!Ó!ˆ6×!×!×!×!×!Ð!˜×!×!Ó!˜×!×!×!×!×!×!Ó!ØÐ)Ð Ð)×)×)Õ)Ð ×)×)Ó)Ð×)×)×)×)×)Ð) ×)×)Ó) ×)×)×)×)×)×)Ô)r   c                ó4  — t        t        | dz  «      ¬«      }t        t        dh«      t        «       ¬«      }t	        j
                  t        d¬«      5  t        ||d¬«      j                  t        dt        «       d	¬
«      «       ddd«       y# 1 sw Y   yxY w)u<   Remote egress s prÃ¡zdnou klasifikacÃ­ (fail-closed) selÅ¾e.r7   r8   Úremoter:   zremote classifications deniedr$   Tr    úhttp://example.com©r   r   r   N)
r   r   r
   r   r'   r(   r   r	   r)   r   ©ra   rb   r3   s      r   Ú4test_remote_egress_denied_when_classifications_emptyr�   º   s|   € äœn¨XÐ8HÑ-HÓIÔJ€EÜÜ!*¨H¨:Ó!6Ü'0£{ô€Fô 
�‰”}Ð,KÔ	Lñ 
Ü�6 U¸$Ô?×IÑIÜ ¼)»+ÐSgÔhô	
÷
÷ 
ñ 
ús   Á2BÂBc                óâ   — t        t        | dz  «      ¬«      }t        t        dh«      ¬«      }t	        ||d¬«      j                  t        dt        t        j                  h«      d¬«      «       y	)
uK   Local egress s PUBLIC klasifikacÃ­ je po implicitnÃ­m whitelistu povolenÃ½.r7   r8   rv   r…   Tr    zhttp://localhostr›   N)	r   r   r
   r   r	   r)   r   r   r   rœ   s      r   Ú4test_local_egress_allowed_with_public_classificationrŸ   Ç   s_   € äœn¨XÐ8HÑ-HÓIÔJ€EÜ¬Y¸°yÓ-AÔB€Fäˆv °dÔ;×EÑEÜØÜ%Ô'9×'@Ñ'@Ð&AÓBØ*ô	
õr   c           	     ó˜  — t        t        | dz  «      ¬«      }t        t        dh«      t        t        j
                  h«      ¬«      }t        ||¬«      }t        d¬«      |_        t        j                  t        d¬	«      5  |j                  t        dt        t        j
                  h«      d
¬«      «       ddd«       y# 1 sw Y   yxY w)uD   EgressPermissionGate s base_decision=False odmÃ­tne vÅ¡echny egress.r7   r8   r™   r:   )r!   F)r"   zegress permission deniedr$   rš   r›   N)r   r   r
   r   r   r   r	   r   Úegress_permission_gater'   r(   r   r)   r   )ra   rb   r3   r+   s       r   Ú2test_egress_permission_gate_deny_all_when_disabledr¢   ×   s¨   € äœn¨XÐ8HÑ-HÓIÔJ€EÜÜ!*¨H¨:Ó!6Ü'0Ô2D×2KÑ2KÐ1LÓ'Mô€Fô �f¨Ô.€Dä"6ÀUÔ"K€DÔÜ	�‰”}Ð,FÔ	Gñ 
Ø�‰ÜØ$Ü )Ô+=×+DÑ+DÐ*EÓ FØ0ôô	
÷
÷ 
ñ 
ús   Â6C Ã C	)r   z$frozenset[DataClassification] | Noner   rP   r   rP   Úreturnr   )r£   ÚNone)&Ú__doc__Ú
__future__r   ÚbuiltinsrY   Ú_pytest.assertion.rewriteÚ	assertionÚrewriterW   r   r'   Úcyberbrain26.data_planer   Úcyberbrain26.security_planer   r   r   r   r	   r
   r   r   Ú,cyberbrain26.security_plane.extension_pointsr   r   r,   r5   rp   rr   rz   r   rƒ   r†   r“   r—   r�   rŸ   r¢   © r   r   ú<module>r¯      s¨   ðÙ :å "ç  „ ƒã å 2÷	÷ 	ó 	õ Nð
 =AØ-Øñ	à9ðð ðð ð	ð
 óó"ó
eó:ó "óCó(
ó.	Mó%ó".ó *ó 

óô 
r   