Ë
    ÛJêjPH  ã                  ó²   — U d Z ddlm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	 ddl
mZmZ ddlmZmZmZ dZded	<   d
Zded<   dZded<    G d„ d«      ZdgZy)z9Linux code execution isolated with Bubblewrap namespaces.é    )ÚannotationsN)ÚPath)ÚAnyÚFinal)ÚENV_WORKSPACES_ROOTÚensure_workspaces_rootÚresolve_workspacez/etc/resolv.confz
Final[str]ÚRESOLVER_PATHi    z
Final[int]ÚNETWORK_TMPFS_SIZE_BYTESé   ÚNETWORK_MAX_PROCESSESc                  óî   — e Zd ZdZdddddddd	d
ddœ
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Zedd„«       Zedd„«       Zdd
ddœ	 	 	 	 	 	 	 	 	 	 	 dd„Zddœ	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Z	ddœ	 	 	 	 	 dd„Z
dd„Zy
)ÚBubblewrapExecutionProvideraý  Run Python or shell code in an isolated, disposable Linux filesystem.

    The sandbox gets read-only access to system runtime directories, a private
    temporary directory, and one writable workspace mount. Network, process,
    IPC, and other namespaces are unshared. There is deliberately no
    unsandboxed fallback when Bubblewrap is unavailable or cannot start.

    ``allow_network`` is the one profile that changes that, and it changes
    exactly three things: the network namespace is *shared* with the host instead
    of unshared, the ``/etc/resolv.conf`` of the host is bound read-only so
    a target named in the engagement can be resolved at all, and the ``/tmp``
    tmpfs gets a size ceiling (with the matching ``RLIMIT_NPROC``). Nothing
    else about the sandbox differs. Every filesystem, PID, IPC and UTS
    isolation, the read-only ``/usr``, the disposable ``/tmp`` and the single
    writable workspace bind stay exactly as they are, and the flag is ``False``
    by default, so a caller that does not ask for it gets byte-for-byte the
    argument vector this class has always produced. It is set only from an
    authenticated engagement profile (see
    ``execution_plane.secure_gateway.engagement_profile_allows_network``) and
    only for the pentest role; it is not a general "let code use the internet"
    switch, and it says nothing about untrusted code - a process in a shared
    network namespace is still confined by everything else listed here, and
    still must not be described as safe to run hostile code.

    The namespace part of that claim is only true because the vector is
    ``--unshare-all --share-net`` and not ``--share-net``. bwrap applies no
    unsharing by default, so omitting ``--unshare-all`` silently shares the
    host's PID namespace too - which is a credential-exfiltration primitive, not
    a weaker sandbox. The regression test for this is
    ``test_the_network_profile_keeps_its_own_pid_namespace`` in
    ``tests/test_execution_provider.py``, which launches bwrap and reads
    ``/proc/1/comm``; an argv-string assertion cannot catch this class of bug.

    The writable workspace is **not** an arbitrary host directory. Because the
    workspace is bind-mounted read-write, a workspace such as ``/root/.ssh``
    would undo every other control in this class: read-only ``/usr``, disposable
    ``/tmp`` and unshared namespaces all keep their value only if the code
    cannot reach the rest of the host through the one mount it is given. A
    workspace is therefore confined to the sanctioned root
    ``<data dir>/workspaces`` (:func:`~cyberbrain26.product.local_store.workspaces_root`).
    The default root is the single source of truth for the data directory, so
    ``CYBERBRAIN_DATA_DIR`` moves it. Deployments that genuinely need another
    root pass ``workspaces_root`` explicitly or set
    ``CYBERBRAIN_WORKSPACES_ROOT``; widening the confinement is then the
    caller's explicit decision, recorded in configuration, never a silent
    fallback to the old permissive behaviour.
    Úbwrapz/usr/bin/python3z/usr/bin/shg      >@i   é
   é   i@B NF)
Úbubblewrap_binaryÚpython_executableÚshell_executableÚmax_timeout_secondsÚmax_memory_mbÚmax_cpu_secondsÚmax_file_size_mbÚmax_output_bytesÚworkspaces_rootÚallow_networkc       
        ó:  — |dk  rt        d«      ‚|dk  s
|dk  s|dk  rt        d«      ‚|dk  rt        d«      ‚|| _        || _        || _        || _        || _        || _        || _        || _        |	rt        |	«      j                  «       nd | _        |
| _        y )Nr   z$max_timeout_seconds must be positivez resource limits must be positivez!max_output_bytes must be positive)Ú
ValueErrorr   r   r   r   r   r   r   r   r   Ú
expanduserÚ_workspaces_rootr   )Úselfr   r   r   r   r   r   r   r   r   r   s              úV/home/oem/CyberBrain26-unified/src/cyberbrain26/execution_plane/bubblewrap_provider.pyÚ__init__z$BubblewrapExecutionProvider.__init__U   s²   € ð  !Ò#ÜÐCÓDÐDØ˜AÒ °AÒ!5Ð9IÈQÒ9NÜÐ?Ó@Ð@Ø˜qÒ ÜÐ@ÓAÐAØ!2ˆÔØ!2ˆÔØ 0ˆÔØ#6ˆÔ Ø*ˆÔØ.ˆÔØ 0ˆÔØ 0ˆÔÙFU¤ _Ó 5× @Ñ @Ô BÐ[_ˆÔØ*ˆÕó    c                 ó   — y)NÚ
bubblewrap© )r!   s    r"   Úprovider_namez)BubblewrapExecutionProvider.provider_namet   s   € àr$   c                ó6  — | j                   �| j                   j                  «       S t        j                  j	                  t
        «      }|�E|j                  «       r5t        |j                  «       «      j                  «       j                  «       S t        «       S )ap  Return the directory a writable workspace must live inside.

        Precedence: the ``workspaces_root`` constructor argument, then the
        ``CYBERBRAIN_WORKSPACES_ROOT`` environment variable, then the default
        sanctioned root under the data directory. The default root is created
        on demand with owner-only permissions so a fresh installation works;
        an explicitly configured root is only resolved, never created, because
        it belongs to the caller.

        The value is resolved per call rather than at construction time so the
        root always reflects the current environment.
        )
r    ÚresolveÚosÚenvironÚgetr   Ústripr   r   r   )r!   Ú	env_values     r"   Úallowed_workspaces_rootz3BubblewrapExecutionProvider.allowed_workspaces_rootx   st   € ð × Ñ Ð,Ø×(Ñ(×0Ñ0Ó2Ð2Ü—J‘J—N‘NÔ#6Ó7ˆ	ØÐ  Y§_¡_Ô%6Ü˜	Ÿ™Ó)Ó*×5Ñ5Ó7×?Ñ?ÓAÐAÜ%Ó'Ð'r$   é   )Útimeout_secondsÚ	workspacer   c          	   ƒ  óz  K  — | j                   xs |}|j                  «       j                  «       }|dv r!| j                  ddg}|j	                  d«      }	n$|dv r| j
                  d|g}d}	nt        d|› �«      ‚d}
|�[t        |«      j                  «       j                  d	¬
«      }
t        |
| j                  ¬«      }
|
j                  «       st        d«      ‚t        j                  | j                  «      }|€t!        d| j                  ›d�«      ‚t#        t%        |«      | j&                  «      }|dk  rt        d«      ‚|
€Ht)        j*                  d¬«      5 }| j-                  |||	t        |«      ||¬«      ƒ d{  –—† cddd«       S | j-                  |||	|
||¬«      ƒ d{  –—† S 7 Œ,# 1 sw Y   Œ*xY w7 Œ­w)a  Execute code and return captured output and process status.

        ``allow_network`` mirrors the constructor argument for the one caller
        that decides per call rather than per provider
        (``AuthorizedExecutionService``, which derives it from the
        authenticated engagement profile). The two are combined with ``or``, so
        a call can grant what the constructor granted but never take a grant
        away: the narrower of the two is always the default, and neither can
        widen the sandbox on its own.
        >   ÚpyÚpythonú-Iú-úutf-8>   ÚshÚshellú-cNz unsupported execution language: T)Ústrict)Úrootz'workspace must be an existing directoryzBubblewrap executable z7 is unavailable; refusing to run code without a sandboxr   z timeout_seconds must be positivezcyberbrain26-exec-)Úprefix©r   )r   r.   Úlowerr   Úencoder   r   r   r   r*   r	   r0   Úis_dirÚshutilÚwhichr   ÚRuntimeErrorÚminÚfloatr   ÚtempfileÚTemporaryDirectoryÚ_run)r!   ÚcodeÚlanguager2   r3   r   Únetwork_allowedÚlanguage_keyÚcommandÚstdinÚworkspace_pathÚbinaryÚtimeoutÚtemp_dirs                 r"   Úexecutez#BubblewrapExecutionProvider.execute�   sÞ  è ø€ ð& ×,Ñ,Ò=°ˆØ—~‘~Ó'×-Ñ-Ó/ˆØÐ+Ñ+Ø×-Ñ-¨t°SÐ9ˆGØ—K‘K Ó(‰EØ˜_Ñ,Ø×,Ñ,¨d°DÐ9ˆGØ‰EäÐ?À¸zÐJÓKÐKð '+ˆØÐ Ü! )›_×7Ñ7Ó9×AÑAÈÐAÓNˆNÜ.Ø T×%AÑ%AôˆNð "×(Ñ(Ô*Ü Ð!JÓKÐKä—‘˜d×4Ñ4Ó5ˆØˆ>ÜØ(¨×)?Ñ)?Ð(Bð C9ð 9óð ô ”e˜OÓ,¨d×.FÑ.FÓGˆØ�aŠ<ÜÐ?Ó@Ð@àÐ!Ü×,Ñ,Ð4HÔIð ÈXØ!ŸY™YØ˜G U¬D°«N¸GØ"1ð 'ó ÷ ÷ñ ð
 —Y‘YØ�G˜U N°GØ)ð ó 
÷ 
ð 	
ð	ø÷ð úð

ús<   ‚EF;Å$F-Å>F+Å?F-Æ$F;Æ&F9Æ'F;Æ+F-Æ-F6Æ2F;r@   c          	   ƒ  óz  K  — |g|rdnd¢d‘d‘d‘d‘d‘}dD ]0  }t        |«      j                  «       sŒ|j                  d||f«       Œ2 |r9t        t        «      j	                  «       r|j                  dt        t        f«       dd	g}	|rd
t        t        «      dd	g}	|j                  ddddg|	¢d‘t        j                  |«      ‘d‘d‘d‘d‘d‘d‘d‘d‘d‘d	‘d‘| j                  ||¬«      ¢­«       t        j                  «       }
t        j                  ||�t        j                  j                  nt        j                  j                   t        j                  j                  t        j                  j                  dœŽƒ d {  –—† }|j"                  €J ‚|j$                  €J ‚t        j&                  | j)                  |j"                  «      «      }t        j&                  | j)                  |j$                  «      «      }d}	 |�e|j*                  €J ‚|j*                  j-                  |«       |j*                  j/                  «       ƒ d {  –—†  |j*                  j1                  «        t        j2                  |j5                  «       |¬«      ƒ d {  –—†  t        j<                  ||«      ƒ d {  –—† \  \  }}\  }}| j>                  |jA                  dd¬ «      |jA                  dd¬ «      |jB                  |||t        j                  «       |
z
  d!œS 7 �Œ‘7 ŒË7 Œ†# t6        $ r. d}|j9                  «        |j5                  «       ƒ d {  –—†7   Y Œºt        j:                  $ rL |j9                  «        |j5                  «       ƒ d {  –—†7   t        j<                  ||d¬«      ƒ d {  –—†7   ‚ w xY w7 �Œ­w)"N)ú--unshare-allz--share-net)rX   z--die-with-parentz--new-sessionz	--ro-bindz/usr)z/libz/lib64z--tmpfsz/tmpz--sizez--devz/devz--procz/procz--bindz
/workspacez--chdirz
--clearenvz--setenvÚPATHz/usr/bin:/binÚHOMEz--r@   )rQ   ÚstdoutÚstderrF)rT   T)Úreturn_exceptionsr9   Úreplace)Úerrors)Úproviderr[   r\   Úreturn_codeÚ	timed_outÚstdout_truncatedÚstderr_truncatedÚduration_seconds)"r   ÚexistsÚextendr
   Úis_fileÚstrr   r+   ÚfspathÚ_limited_commandÚtimeÚ	monotonicÚasyncioÚcreate_subprocess_execÚ
subprocessÚPIPEÚDEVNULLr[   r\   Úcreate_taskÚ_read_cappedrQ   ÚwriteÚdrainÚcloseÚwait_forÚwaitÚTimeoutErrorÚkillÚCancelledErrorÚgatherr(   ÚdecodeÚ
returncode)r!   rS   rP   rQ   r3   rT   r   Ú
bwrap_argsÚruntime_pathÚ
tmpfs_argsÚstartedÚprocessÚstdout_taskÚstderr_taskrb   r[   rc   r\   rd   s                      r"   rK   z BubblewrapExecutionProvider._runÍ   së  è ø€ ð ð
ñ$ !ñ 1à'ð'
ð*  ð+
ð, ð-
ð. ð/
ð.  ð/
ð. "(ð/
ˆ
ð2 /ò 	MˆLÜ�LÓ!×(Ñ(Õ*Ø×!Ñ! ;°¸lÐ"KÕLð	Mñ œT¤-Ó0×8Ñ8Ô:ð ×Ñ˜{¬M¼=ÐIÔJð "+¨FÐ 3ˆ
Ùð
 #¤CÔ(@Ó$AÀ9ÈfÐUˆJØ×Ñà˜Ø˜'ðð ðð ð	ô Ÿ)™) IÓ.ð	ð 1=ð	ð
 ðð
 (ðð ðð ðð #ðð %4ðð ðð #ðð %+ðð ðð ×&Ñ& w¸mÐ&ÓLñô	
ô —.‘.Ó"ˆÜ×6Ñ6ØØ-2Ð->”'×$Ñ$×)Ò)ÄG×DVÑDV×D^ÑD^Ü×%Ñ%×*Ñ*Ü×%Ñ%×*Ñ*ò	
÷ 
ˆð �~‰~Ð)Ð)Ð)Ø�~‰~Ð)Ð)Ð)Ü×)Ñ)¨$×*;Ñ*;¸G¿N¹NÓ*KÓLˆÜ×)Ñ)¨$×*;Ñ*;¸G¿N¹NÓ*KÓLˆØˆ	ð	ØÐ Ø—}‘}Ð0Ð0Ð0Ø—‘×#Ñ# EÔ*Ø—m‘m×)Ñ)Ó+×+Ð+Ø—‘×#Ñ#Ô%Ü×"Ñ" 7§<¡<£>¸7ÔC×CÐCô HOÇ~Á~Ø˜óH
÷ B
Ñ>Ñ"ˆÐ!Ñ$> VÐ-=ð ×*Ñ*Ø—m‘m G°I�mÓ>Ø—m‘m G°I�mÓ>Ø"×-Ñ-Ø"Ø 0Ø 0Ü $§¡Ó 0°7Ñ :ñ	
ð 		
ð?
ùð ,øàCùÜò 	!ØˆIØ�L‰LŒNØ—,‘,“.× Ó Ü×%Ñ%ò 	Ø�L‰LŒNØ—,‘,“.× Ñ Ü—.‘. ¨kÈTÔR×RÑRØð		úðB
ûs“   ‚2N;µEN;Æ	LÆ
A>N;È	AL! ÉLÉAL! ÊLÊL! ÊN;Ê6N8Ê7A$N;ÌL! ÌL! Ì!.N5ÍMÍN5ÍN;Í5N5ÎNÎ N5Î-N0Î.N5Î5N;c          
     óð   — d}|r|dt         › d�z  }|dz  }| j                  dd|t        | j                  «      t        | j                  dz  dz  «      t        | j
                  dz  dz  «      |d   g|d	d
 ¢S )aù  Apply CPU, address-space, and output-file limits before exec.

        With ``allow_network`` False - the default - the source string below is
        byte-for-byte the one this method has always passed to ``-c``. The only
        difference in the other profile is the extra ``RLIMIT_NPROC`` block
        spliced into that same source text, never an extra argv element: an
        extra slot would shift ``sys.argv[4]`` and make the limiter exec itself
        instead of the requested program.
        aV  import os, resource, sys
cpu, memory, file_size = map(int, sys.argv[1:4])
for key, limit in ((resource.RLIMIT_CPU, cpu), (resource.RLIMIT_AS, memory), (resource.RLIMIT_FSIZE, file_size)):
    _, hard = resource.getrlimit(key)
    if hard != resource.RLIM_INFINITY:
        limit = min(limit, hard)
    resource.setrlimit(key, (limit, limit))
znproc = a?  
_, hard = resource.getrlimit(resource.RLIMIT_NPROC)
if hard != resource.RLIM_INFINITY:
    nproc = min(nproc, hard)
try:
    resource.setrlimit(resource.RLIMIT_NPROC, (nproc, nproc))
except (OSError, ValueError) as exc:
    sys.stderr.write('RLIMIT_NPROC: ' + str(exc) + '\n')
    sys.stderr.flush()
    os._exit(126)
zKexecutable = sys.argv[4]
os.execv(executable, [executable, *sys.argv[5:]])
r7   r<   i   r   é   N)r   r   ri   r   r   r   )r!   rP   r   Úlimiters       r"   rk   z,BubblewrapExecutionProvider._limited_commandH  s³   € ð<ð 	ñ ØØÔ0Ð1ð 	2&ð 	&ñˆGð 	ðBñ	
ˆð
 ×"Ñ"ØØØÜ�×$Ñ$Ó%Ü�×"Ñ" TÑ)¨DÑ0Ó1Ü�×%Ñ%¨Ñ,¨tÑ3Ó4Ø�A‰Jð

ð �Q�Rˆ[ð

ð 
	
r$   c              ƒ  óV  K  — g }d}d}|j                  d«      ƒ d {  –—† x}rn| j                  |z
  }|dkD  r$|d | }|j                  |«       |t        |«      z  }t        |«      t	        |d«      kD  rd}|j                  d«      ƒ d {  –—† x}rŒndj                  |«      |fS 7 Œ‡7 Œ­w)Nr   Fi   Tr$   )Úreadr   ÚappendÚlenÚmaxÚjoin)r!   ÚstreamÚchunksÚretainedÚ	truncatedÚchunkÚ	remainingÚkepts           r"   rt   z(BubblewrapExecutionProvider._read_capped{  s¹   è ø€ Ø ˆØˆØˆ	Ø#Ÿ[™[¨Ó/×/Ð/ˆeÐ/Ø×-Ñ-°Ñ8ˆIØ˜1Š}Ø˜Z˜iÐ(�Ø—‘˜dÔ#ØœC ›IÑ%�Ü�5‹zœC 	¨1Ó-Ò-Ø �	ð $Ÿ[™[¨Ó/×/Ð/ˆeÑ/ð �x‰x˜Ó Ð*Ð*ð 0øÐ/ús(   ‚B)�B%žA,B)Â
B'ÂB)ÂB)Â'B))r   ri   r   ri   r   ri   r   rH   r   Úintr   r—   r   r—   r   r—   r   zstr | Path | Noner   ÚboolÚreturnÚNone)r™   ri   )r™   r   )rL   ri   rM   ri   r2   r—   r3   z
str | Noner   r˜   r™   údict[str, Any])rS   ri   rP   ú	list[str]rQ   zbytes | Noner3   r   rT   rH   r   r˜   r™   r›   )rP   rœ   r   r˜   r™   rœ   )r�   zasyncio.StreamReaderr™   ztuple[bytes, bool])Ú__name__Ú
__module__Ú__qualname__Ú__doc__r#   Úpropertyr(   r0   rV   rK   rk   rt   r'   r$   r"   r   r   $   sŽ  „ ñ.ðf ")Ø!3Ø -Ø%)Ø Ø!Ø "Ø )Ø-1Ø#ñ+ð ð+ð ð	+ð
 ð+ð #ð+ð ð+ð ð+ð ð+ð ð+ð +ð+ð ð+ð 
ó+ð> òó ðð ò(ó ð(ð2  "Ø $Ø#ñ>
àð>
ð ð>
ð
 ð>
ð ð>
ð ð>
ð 
ó>
ðP $ñy
àðy
ð ðy
ð ð	y
ð
 ðy
ð ðy
ð ðy
ð 
óy
ðx <Añ1
Ø ð1
Ø48ð1
à	ó1
ôf+r$   r   )r    Ú
__future__r   rn   r+   rD   rI   rl   Úpathlibr   Útypingr   r   Ú cyberbrain26.product.local_storer   r   r	   r
   Ú__annotations__r   r   r   Ú__all__r'   r$   r"   ú<module>r¨      sg   ðÚ ?å "ã Û 	Û Û Û Ý ß ÷ñ ð /€ˆzÓ .ð
 (9Ð ˜*Ó 8ð
 %(Ð �zÓ '÷c+ñ c+ðL )Ð
)�r$   