From f6f42d6e5873ff2f283909f777d7e99070e78103 Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:49:26 +0530 Subject: [PATCH 1/7] fix: validate git refs before archive URL (#53) --- backend/app/main.py | 33 ++++++++++++++++++++++++++++++++- package-lock.json | 6 ++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/backend/app/main.py b/backend/app/main.py index ac02df8..eaf5255 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,5 @@ from __future__ import annotations - +import subprocess import asyncio import functools import json @@ -65,6 +65,18 @@ ScanResponse, VerifyResponse, ) + + +def validate_git_ref(repo_url: str, ref: str) -> bool: + try: + # Use ls-remote to check if ref exists in the remote repo + subprocess.check_output(["git", "ls-remote", repo_url, ref]) + return True + except subprocess.CalledProcessError: + return False + + + from .remediation.engine import propose_fixes from .reports.evidence_pack import build_evidence_pack from .reports.pdf_builder import generate_audit_pdf, generate_org_audit_pdf @@ -142,6 +154,25 @@ def health(): "scanners": scanners, } +@app.post("/scan-url") +async def scan_url( + background_tasks: BackgroundTasks, + repo_url: str = Form(...), + ref: str = Form("main"), + project_name: str = Form("project"), +): + if not validate_git_ref(repo_url, ref): + raise HTTPException(status_code=400, detail=f"Invalid git ref: {ref}") + + job_id = next(tempfile._get_candidate_names()) + job_dir = WORK_ROOT / job_id + ensure_dir(job_dir) + archive_path = job_dir / "repo.zip" + repo_dir = job_dir / "repo" + ensure_dir(repo_dir) + zip_url = github_zip_url(repo_url, ref=ref) + + @app.get("/api/health/ollama", tags=["Health"]) async def ollama_health(): diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5bb9692 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PatchPilot", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From a36e68c29938cc616dd0933c9a6f90ef7fc5942c Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:36:09 +0530 Subject: [PATCH 2/7] fix: remove duplicate scan_url endpoint --- backend/app/main.py | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index eaf5255..7a1d04a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -154,26 +154,6 @@ def health(): "scanners": scanners, } -@app.post("/scan-url") -async def scan_url( - background_tasks: BackgroundTasks, - repo_url: str = Form(...), - ref: str = Form("main"), - project_name: str = Form("project"), -): - if not validate_git_ref(repo_url, ref): - raise HTTPException(status_code=400, detail=f"Invalid git ref: {ref}") - - job_id = next(tempfile._get_candidate_names()) - job_dir = WORK_ROOT / job_id - ensure_dir(job_dir) - archive_path = job_dir / "repo.zip" - repo_dir = job_dir / "repo" - ensure_dir(repo_dir) - zip_url = github_zip_url(repo_url, ref=ref) - - - @app.get("/api/health/ollama", tags=["Health"]) async def ollama_health(): """ @@ -677,7 +657,11 @@ async def scan( ) return {"job_id": job_id, "project_name": project_name, "status": "running"} - +if not validate_git_ref(repo_url, ref): + raise HTTPException( + status_code=400, + detail=f"Invalid git ref: {ref}", + ) @app.post("/scan-url") async def scan_url( background_tasks: BackgroundTasks, From 898e651020fb36d03d3933bd55fb2b70d781db5d Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:39:00 +0530 Subject: [PATCH 3/7] fix: validate git refs before archive URL --- backend/app/main.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 7a1d04a..d8b623b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,5 @@ from __future__ import annotations -import subprocess + import asyncio import functools import json @@ -8,6 +8,7 @@ import random import re import shutil +import subprocess import tempfile import uuid from datetime import datetime, timezone @@ -65,18 +66,6 @@ ScanResponse, VerifyResponse, ) - - -def validate_git_ref(repo_url: str, ref: str) -> bool: - try: - # Use ls-remote to check if ref exists in the remote repo - subprocess.check_output(["git", "ls-remote", repo_url, ref]) - return True - except subprocess.CalledProcessError: - return False - - - from .remediation.engine import propose_fixes from .reports.evidence_pack import build_evidence_pack from .reports.pdf_builder import generate_audit_pdf, generate_org_audit_pdf @@ -87,6 +76,15 @@ def validate_git_ref(repo_url: str, ref: str) -> bool: from .scanners.semgrep import run_semgrep from .utils.fs import ensure_dir, safe_rmtree, unzip_to_dir + +def validate_git_ref(repo_url: str, ref: str) -> bool: + try: + # Use ls-remote to check if ref exists in the remote repo + subprocess.check_output(["git", "ls-remote", repo_url, ref]) + return True + except subprocess.CalledProcessError: + return False + _MAX_UPLOAD_MB_RAW = os.environ.get("MAX_UPLOAD_MB") RANKER = load_ranker() @@ -657,11 +655,6 @@ async def scan( ) return {"job_id": job_id, "project_name": project_name, "status": "running"} -if not validate_git_ref(repo_url, ref): - raise HTTPException( - status_code=400, - detail=f"Invalid git ref: {ref}", - ) @app.post("/scan-url") async def scan_url( background_tasks: BackgroundTasks, @@ -669,6 +662,12 @@ async def scan_url( ref: str = Form("main"), project_name: str = Form("project"), ): + if not validate_git_ref(repo_url, ref): + raise HTTPException( + status_code=400, + detail=f"Invalid git ref: {ref}", + ) + job_id = next(tempfile._get_candidate_names()) job_dir = WORK_ROOT / job_id ensure_dir(job_dir) From 5a83b967d140b064c7f44a9e23f801a09ee5779b Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Sun, 5 Jul 2026 14:00:20 +0530 Subject: [PATCH 4/7] Fix merge conflict markers in main.py (scan_url endpoint) --- backend/app/__pycache__/main.cpython-313.pyc | Bin 10907 -> 80136 bytes .../app/__pycache__/models.cpython-313.pyc | Bin 2677 -> 4200 bytes backend/app/main.py | 178 ++++++++++++++++-- .../__pycache__/engine.cpython-313.pyc | Bin 1948 -> 1958 bytes .../__pycache__/templates.cpython-313.pyc | Bin 1176 -> 1164 bytes .../__pycache__/evidence_pack.cpython-313.pyc | Bin 3712 -> 5472 bytes .../__pycache__/verify.cpython-313.pyc | Bin 4958 -> 4977 bytes .../__pycache__/gitleaks.cpython-313.pyc | Bin 2250 -> 2921 bytes .../scanners/__pycache__/osv.cpython-313.pyc | Bin 3489 -> 5650 bytes .../__pycache__/semgrep.cpython-313.pyc | Bin 2440 -> 3687 bytes .../utils/__pycache__/exec.cpython-313.pyc | Bin 1909 -> 1897 bytes .../app/utils/__pycache__/fs.cpython-313.pyc | Bin 2046 -> 5164 bytes 12 files changed, 162 insertions(+), 16 deletions(-) diff --git a/backend/app/__pycache__/main.cpython-313.pyc b/backend/app/__pycache__/main.cpython-313.pyc index ec94d8cfb7cfa08ca16139b6ac2e3ecf5ca44c9d..cac27d29cc82a1b3893b2bdad748a11bf2235b57 100644 GIT binary patch literal 80136 zcmc${3wRsXc_usq3@!vg03<*Xyn`2s_nSycqORsm6hVrlIfSBGiW!Q8Bw7?m4@k*k z;+V11MoQY6vg3+!+=y;2rrUJGY`3+$+ucg?O--{2AgBh4aTgi4~N9ptY z|NEW63_uFBo#cO>j>LmAXTEctbH4N4&NuI+rI|QfCyZT>*#4R0{()|kD@Bg*?|sqC zanErgC-S|Vo0oVO#WlSeNyC1%l9v4nlE8j-l8*iAB|ZB!NCx(6l#J{*MM`16CdtHp zQ>9e=YJ1IYvt)LsNolNnq1WQJN>&!v_1fI&QhKc33@L-U$mTOexcyC1ts@ zrEIrTa=LS*9Cxmi>&}z%-1$;I%V+2LG!yGx`Jcd1nBE|bbw znyGh_d$Y9JT`rZg`_$eFccoP6u9B+Qy}7sAT_e@7cv^3*yH2We*Gu)dw}{r>26v;> z=-wi2aW_d#?q;dk-6FNPw@O>x+oWyo?b3F4tJLb=A?3l$h{dye zf5ZKd^bm_Xdk?#hNJm&ar}xwDhoy(zBhrZbGty^V+!6iIHD$WQe}~?P$Ca3v+rrCn zY3xW{3D**PAdMGtN5(2*ccVs*JK`-tiUK{ri+R$qBPU8QwnvV&Xkuj?J913Suhns! z97+>MbVD~|g(Uo-q?1ZX(&UjWlu{rTibbs&=~O)@x*p^u-!IXJVx_Fzywc+Fe2)92 z#-T(kReLdYWJ)P#bOJq}8v02><6}{=OsUn*?H}ei>Cq#P4*lc(aO#NqkNA~R$1z?9 z(844Bp?B`jA#PGiR^Ug|8SMZ5C|JpV#5eSR?uR(yS4wkn2M%&vDvisjp?^sN={n*Q zH;d)Po6~_Q=Di^Q>~uTKli} zmnT*$5MoVkeBOQ*eM>gaYgOpF1nAFg09`LOU=$i#HR6^8=*Pt`h)ucNBZBmK@kz0n z#hz&3q(p^%gRiBBo9*!>s97J__)m!1@VQ{2jOJth8@_$6_BEd7g(oY;Djmwr>+ zA^lb%C!Skd0^Nz{#ho$eFNt3kcd=5=iC+ zIg*X__keom#9fAZoiT{Ny+Mnv1bDcWOKs!iO-6I>*{)TgRy&19E#2N zr$up1|G&CHj$x%njE`qcj^~^l-uA&k55 z@dx51agtJ`Zvaa@zjpN|-icomPs!sUeN%iv^szkO+OWncwZ`8Ur`Oc@?G5_$=#g%= zg1;_GYv5nnunu)+Jd%qUJEPRt%!`-BS-Gw1DqY^7*3(KZ@$2H5HTAx{L5{}~a-3b0 z;}18;F_)0zvukqv(FQp_r>^I(i;u6#@tqBFd|rGah5^1V&acVw$Hz6r+>zr3Zd6Y+ z714b~T({8w{~qIZYm2yJeB}}&Vk-G)>Zs2*H8VOhG3E33S8Afh@zELY%)}{g)JWmn zl+Oz&VQ_Tj1pW3+_-87OQEU6?*vaG4)U0oOc+`K=A2sY7_0O~o_C$q!6O&$qQ_`tu z+Wz6;!LG-~ywg;CRQKSlS2`Ot40#`&_4;R`CeQTb)aW>6K+);atZ!t(H!^cV@{W#A zPfblC(b6$6Bp#R;AD{G|8I`=1+NhbphrIr2v=?nuW9i}y@|~LS9aoajnUwZXzxTk@ zxOXzD+c)8z97kG_*wsJW)!)%IGCb7QFYX%{I?y#Fj*;H2@9T=Dj(f*vrza=I zFc4@$%CzJipBS5&l8`x-8ayJ6`c8VKsLek%C85HRW3#?7>ft*)FjA#8Y8^vUX1pWE zCVb;~?vI*c36D%2jhfIJFCJ3daojsIGIqv0GLB)DCXUXsane)j_|d4H{d%XpzHzT_ z>@0we9gC(?Vyxh3R>F;c>^+(fV$y7X*fTZl%X}?!O>!*Bj%cmsvs4^9ELGE|d z9*c-9i-#2*)$W^kENViaOoLPwS6*n}6vmxqi(^1K?w&d-=T+Wm)PhuTY}BV@HVt{F zr(~F@S$+^J$#T#uO&mL`Gt@<4Bo*> zs?tjLQ~uKcP7F>=PR+QmAm?-qb&Yim=$3AVnh`ai zy?zXEr7)LyVrFJ~M}7Sy#(eU`lz(PNOXIdCHMw!iwz>xVYh0Tghi+$T3E!C`ol|Fggf{#~;&0~2 zQA{-~zayj5(??DLtZw?OBqBEpehU%51;IPqJa^la{*}rv4LmjQb3cSf+>;h3zrbe+ z7xYek&QOoePtKfJ()IN53xFLfQkN+xzuH^Lb9{f|;YUCDm9!@mWY0Dt8Qeu_He!O6L5#3A0 z0;`QJu&7a8eOl=uR3lLrr6UNU26^<(V$<^;mw+V4vHmnuC!=~y6+&{xSRsB)Prd&H z&@(oYGbbj-PDHhso>Bej(MfD&l^SW33jH(!KMk_WB^{y)^l}B#G2DEG9zKR(o_k-< zIdU)VdS=&BO2}TdQWmgpUCl1I__=33w{#$sz2()iK=z(hK=eJ+x0D-l)U0#{9NX_2 z1e<<-|NCannz2xEvHn7RD5ErFDVy)UqvxzyKQ7w5P#JOL1I(E6pUo)HN4LwD(>Bs0;Y=Iz2zBZasqDB!B z45@Eh$%FT&&I-&15Gb*-j7lv2i`gl}=Rsj2G!wSQAt_eEd3aIR%JWJ?k%J4*LX;kgQ%YrCt@H z*tvr`zos4&2SO=3$4z;X<}fMaFsM0@DT{=BdY?{A_0SqH*J=G4QBVJrk?+BM;uyF% zte3QU^!bxQ1I2wEe_!-#Txgw7cZ`tUCj8loxf+g@j}1pgibl@NDQk2b2=o~86ai5J zRqADuvpDhXWvm}Djyvp7(hg^-F{cu17C_9R0-piAW9%9#P-5|4(c&>!xcD0d@te@+ zBD`Opq0K;;gMN?k3PDV$ywWXF$=9)R-b+K*EHNaMg3+Vjx#F*DLGv$)Ed)Mgc z(TT~?qZ5-8GiP0+zH!$iNH`ZZt{ET(@3`v}QJwxe(>{RtUBE75-ea?qlV_<2P?V>! zr@2}h8eCpUnv(pkiDRr%rJf1DYaH7~ohfP^=<91c(57yhb6NLo+Kr9PP0dm-2I06o z5XJ{y=A#B8-=}70`sY&ElBbpZ&UFAp^>W*yf^YPccNi!yCDxfqujC^D<57^!BeT-v zzPbF^0LX~UHFm-~cG7hWee#a)aDmj-O!=ckuX=r_qk4&m*y*T_TKHJhFf2D9n%d?+ z>l^EsoIszWS^$HtVfYoq<2ZPQNBki7%rT0ti^d~rI!K6Bj$7JhWC}BOpE7)QIu_MW zP8~lEO56bEz=s!CX_OvC2c${X%`v$~qoi;P0wZ>Htow3Ld9R<)jw^P_SUXKdiz6}Q zO}p{e@W)tB+L3{RZ5dCW{qosBPF0|$Gi2=wTl<36zUyMh`r!QjRg3-UQ(r!{*b%b0 zfSd)V&a`TGhV5lH>}AWJ3fo(5*jrxBiWHPye(2Ie%iEW$R(b*zdqM?!!`XY!?Z0Ks z4qHou*3u<^$XXT2F1q;mg~y+LVnK+s?*2>XpF6KqTyMYGIuObkywN(aqPtvqsd8!G z+qu=j)`10mAZPGa@uo;&S;SR=jlh}lK^o`SiYkNlmOHszNqMBur9R4$ABF5Kf1ADc z=kFT1+z0s&QsqRsmA`nugiFf^n+t>H!f%?s)BhJefz*BPae8#fXnERl-V(@ZSs7gs z1KF)NjXQ4T6h^X~5vPlGD$~DmLW=1ZcXR;$H-8Yr_vG%bTK+2M>@4K2I{A)m`l~rw zisw5!1?|;lzN1xtwPicv*J^V+cIsa<7!ZHWVg}4>xqK(5e=Sd-coC0sUUTW_=_dBH zk?+*#U)#c-Ze>rm@dVk*6UG@N{jdy(42bFHm0R*(uK%#*tSHTIC38!rzQ{a zqDIuVf^4bO_1~8nn4f>|cje3s5Jl)jeVuM7u^K$53zSn5T23@5xZlT%Mll5?nCi3} zC6jU+P5cok0q=P(XP49op6_bx~u-P|t8rM_XT1*x%E=KdL{_ z)!E}Y5Y>YAj|x5g`v%^jjryG&3Q8#t7@TpHr3F#t1%W>k75oq%RB9!nk3e++Jxai< zQ6ur-$ET#T$i^RwYVjbdnK|RfNYk2?m#dE0aBsA9WLla4^Dr|pr}D4Z1x3LKn z)gS<7^v=vmzNmJ>Hv=k5@1K%pKx{(VqKF8j->0S}(vFT~{gJ*6u)icw+9QFAG&-I0 zeG6v>Nw_={Ol=Mb&8$6Z<`>a$Y;rCje2%N=;4#mRUX}8tCHKJS7@wel_G=!4%3zta2PQo8!@BDm~2fcb*dxwr!-=KO~T{I zBa%$Tqm<%H@uZ;jPvZ@lJfU``EoLYwLzzk}{wwMa85DH~(W|0?sKWlA$-i;xa9zph zFX+&ZF4>Z-WNPL5vO#tJ7gOKF@e?y=ycD{oM}a;hq7%K3&46y4aj6J%!s~Y(lcr9& z#->i4n)10$jrt~zL6izQvtw3*JZ}aBrhj&N8u-PJg2qmc9{1J}edVg?2d!1fC{+pa z*9pmc3gG^_na5^a6@zCXQSnvQL7GN*%I_ML$=Ru6u8LSGBSb9L5cSK1M=l6bCcUV4 z&YW1yJ>BfgvD)n*KaG&6O_B67YEEo^)H?1x-KlH<=+E3{qL4u_6J1O_U)$^npKFtU z2mbo!Z0p+{yg@#Pghq~?A>vA_pUvk|8#XlT~%e}-ZF`1 z_&bEUqJ~-DNgoL7xvKTGt}h&ChC~N~9qd&zQZz*seoMfg&^l9olOMw6naK&C*B{l%6tjtmru=6nW==#k*voXM zNNg3=k%WD^(i{Z|J1vX-1Pmr5U(+X#kI-v{$P)7Mr>IyX_>K{JDos&U`zZKLdSFJ^ zM(~;$ELzVh7U)O%FL=(RN-_GE{m8p&=16SzQJJy?8gEN_9XY;FxnAUH|G8u4%$E61 z$Qc$tA9U2tABY$nVMAWfkQX)-1PujuG^Ugxe$}3RamO<|7Egr>TY`lxubv4Mwgd{h zL-zgWI`0TtrzMh+b+O_?#p0gjEuoCMJ6a^&l9K{?HOogs8I5;zNISRxj*%;=da3Zm z!ncbXFQkDewmB}QUPxUW{$|tCnQ!b01CFYIV^`SG9dvYu96jf{{?1u|!n5-(&Rv*$ z_HzrukF)a@XD&Z+>4{~3xTq;u)bzKG{M*jl%f{!8f#UA#j*zo|LH9rH&V`mpp6l|? zOFNey{o0;|&PZYD<&&3AE*F2z7cOjFNm=NQIGh)IFZ3=smaK0(8t&S;oI()hX4`!4 zx#s!a58tokY&q|7JPn#T3KE zxs__2@BPIc1CJiw^KZwi`g+@*u3GNewzgWR*k7+``!cj|X6O)q zGuPCu)4o}j)y-+&;&h0QC5mn+t|b!n-w-~B4P4m*%{pcCM4RJc zOr=lykL{B|?UUiKN;DlZus#|8UHYUKKe|uGkL^>6+NYGmD*1K@3=7_q>EESK#>74y zRz-y#P9ai!sUVxp9`le&E@DN*t~lX{cbpu;sFbrIgeu_M5JDxXT-;$*=;$%W2@z$c z`4ZC9RRVGaZA(T3nP7pCDw%b7E^-CAg?`SG? zwNR)*YvuRPnPZ}NMr(k>F-W7(%+QAbwGsM1NmH`*hg;r_%qsCK{(5itIJT9k#*+fGiZW@LB2s#>K$Nbo} zxESAP+y&wH(ecqeQ5Vz3F%2H+*5XW~0?~kMewq~X+zaJP#Y+#Z*l*^x$c&$K8R^)g z^%MRPKUi7AC`r2}rl7kXg;J4(flQ7i?FF2)hl1S{>_R|<1w?1#5>f0?J@nDNT|;1o zLB26guQE;guk<8U8R(I*qfrBCVM#k^h6)iCKz~VsH8Tx4k{O{EwZ>iubk*!dFx5wt z6eCJXREwLaO&NS8Ii?vwiKM}lzJ=;p&l4$x*Z^AA4x992znSoPBm_rjTAcI}GgM)sb- z;PCvwswHQ!B52t)-~CgA{UZzdsZvnyO=Csurf;eCrm^9tJKGnw1?`o~?i=>4*M<4s zRTC9z+CJa$2@CC8s=jHgixt|l`1nm@?N7V<77amX-Ad67=L3PEg8{*@YRy~RAGGeB z-~S1V?OFWnO=ISD-JI^TV(&^5n5BACB4@P&be?wdls67RSvIF;93{@-}L-xjV##^Zw zi_Yc!femB@w50IIzxO0wA~t#sEZ%+BdXBAuBntdcY&j-^$F++Ic`)^X=kAvj!l3~JT@gEJtjt~GePm}cS*BrU~2Ff z3B)SYFb0ru8{qJKUYCCgdWX>&rGOZF3H^u34}MQB3EU%@o_LABWVyf}O3;NcBbRa4 zD0CIzjwWCOqZR|jg^}qB$QnhU_C)oBdnU&FEA_zCK*}VGXPlGNg^`RJNl?cOKLlzh zxFpink5D5kb+Rb!9|+Dsg;A*~)G3*#NhPQe-|VTQ;7)1JNZ?~qV?#3n;hglp@T?W{ z+P{bhxEB*=OMiOq{9M>tv^X@sAM(DG^ry||%?l5OjIIx`a_o^*`_sG5?^@J{Qj0#& zBH>mn!5B&{{y>KWWP;dm+xcyGQaGLYj)g1R^wN%(b}aB0O&3fTtm`LfH7iPm5T^ktT6sP>IGYw z=*jv^A)ujIRr4lW2SiSL5@C|831xdF8KE+p$1!K)WGKn8h-d@3t{GAZxp{lmdj~=N5T~S|g_~Cjm z{ITD?#Q7ho13=~8`n_fEETHPSx&a?VBHa@+`)7~3h>Lc?@I;=!F*ylD53o3a+y!!h zx|cQ5Su{VYAIA)xm}HUz=|3Q4&dyq(Hh|U7bZ&Ew`cY}@#KdWDz2rUSC)N76zkW`r zgRxGfUV0UorT`U?iX^PWlT3BuHl*K2&Z|ElP>dnUPUG(h+4q~IfQ3B47( zHkMd0b@UPM7(`EU+AM@!WJCt!LXV~2Mj>MY0_CD%dWz=#3RXZV=c2jKC)<^h?>|H+ z5@~x_Ei)PZ$9lj_hV_8PCqOr>OMp&-_aW&PM1yE-)i7IwltewdPakJ#JnC*{=ejlf zp|AECJe&`@8D9#d>FSC(OnqdRvR(ks?>|k_--MPVu|z;iRMx@=>Fkgy0aZ%GdQ~Nr zJ26K_ok&sH0MP^sf~2{y{>)8UF^#QmeKJ`!bnQlkQ|O=4V%RUNd2f*PV})+e<_y+m zl$-oHz0Hp+9n95*7$18*-%QU z@+|)Av5GmZyp791yVuO#bdTlmCI!xuu8dzTxF4Sd&o(R1c6=3W#>`5XOSxj+VddqJ zWlg@)PLFM^IEThW4HKyT-w@*t_1MlsD9@ z#NxkL)h@AE84X{CM<;jK!Jp;jdp{}oF;DdAetakpzv@SnpUc`b zf+^+1nkP!T*Nz0<(RD6nIFE5$MSJ6W2{H8RE{xJ=uv#2z<}cRInzvl(agd$TuQH22 zcD^N<`oA@|hHK;vpCqfPNYt**_o- zi*>FZ5-%}T7Sv@p6hMs8=%kD6Yp0GKW9(6PjQWEu0oa#Q6QnNdpE?7ht}z%|lg5l4 z9q=;?EHb+yiiPQ;Vs=_XZ3pT*C;TT-!->&ZusTz)%p#4y0U9e*2t!e(AR3uE8IwId z{J=lSlVT4u#R7ffL$_iNxd-;aVssJ!QJdU%h@N5{@0b1$(&q|>VhPj?a97M08k8yx zhpT+fQtmnfw(BT(rjy?BI_dvXS=#Qd;Y!^^Ee}iZItuD3XriDQfpnV+E>;UZHR1Qu znD@o_E*BB5Ua789Gx03I-}z(8bBZ-4t}9bVhmq0`^$fZy<}{Uc67d@EaGZ^cYR(*m z*v&L{0-ClFKWs7H`F#{LS3l%EHHGnK)a(QfQ=G+Mj(NS~@~EF0eQe^?>?!&6xS)2t zr#;YKCzIb=%3m9MiLf7ck!gMmQZ54nGkRe4yjp@ zrvfY{tbAQ_x=PG9*PNx!1^uUVmPQ5Izd3E)mSb~*zpk!s&N)0aMcUT02~)HJjdsns z`n@w}rXOZB@g(+z7aLNbTNeL%JW}tM` zP9nvcaDG7Sd8kY3U~M@A0~)XNXVkbAO3=^xz+A&38jT#EIz^CtxLvRSAXQeMtDF%pYy{D#OAc&Po zD~q_BXr}z0n6=;tDrY5~jq)RGVEx|lC@-8mF$f*92 z3Df}lLe|_7GLd9TQhz3ZnABi>AZtTy%EYJR7fk0`jiQg^&wmyB6aYq>dSX_)Vhq&w!_x2=w_)pf(_TF$=G61Mi-u=ZR(xSE~6 zcsQJ0d9FK>TX3#7QdSW#7vB+j`7}4bnp60~Q=0sK8}+C~s?| zx#g7u-#ZY_`|l3$TyEW+qdez;1UY2i8p$qP;a{u|WjDqKx^yT$wve(GmR)}M z`G=P~R_vjI7QC90niYO!+xJYtlAW)f4i}p?S_Kjkrn!b^BhA zjZ@7b@bB3f;NR0T?)GXpXVnK<&gSMZtwPqG4{~YH14R$q-GXrmV0@q;-xe<3`gZZw zU%a2exhi4PnNxB{BNS|kl-7hxn}VfH;nJ31X-lN6HeA*mENi}5wiSYbf|3v4*KuXF z&`<-^RfacuM~CRWJ1HpO9)w>xMIS&AAy0)HMGxS8%gqQX&A=4Fe>CRa-ASIyZ2l0| zYmS_-eN)iB3C+MfkJxjU+b&Io?6s@8<&m=Tz?PwaI2;)A1R4&an|WF2CL5Yu*X6?J z3zxOaVkoC^LBE<+aPibLr=Xl^Tsi)|9l=d|u51pL?GI)3gl+UcVC(tCoiwW9-Ul8{ ztX&u(H0|H~{~1g9$+pyiEdFX5@3!f$TC@~Tcl7VlUftYQeLy3;md?BF=GQW`6who+ z=_^Zly)YFwuUGQ!EYs^%dWts`ApMOrN`J%2yK_u$MKwr&(`{8cG`fHVG8nLzOhEt>IH)fp7ut)ci{YU=k* ze1EI({Zu{0?L3}-KZ}JqEX?Oo==TeG6#D%lBP#xWIgdtszry6+YW@BeEv2-ul&vh@ zYUf{g7 zd9>{Z8Pv8P6!3j6;Rl7(wjY$~`*x@Opo;IS&GsCf6r>O^fwxQ zSjP94n|`=SNAW5H3i@HA4v&Ahl{){!RvrcYaF>3-ZupUgAJCb9q%~9AYC!rLr6EaG zo?;pdP$jawNVa%{J>9>8E~m2~09k(}=ns(3#WirrKrSt~*pp-HCwNNB4$)>f4-ijOYxf_GnkDV)}57C6MD5JM<){YBc5 z1ba;lSx75VYMcmD&k8}F@PRB(h%3tDQm`S%`f`a&RipBvFqxJvwxpg>_RVV=!m zrc7@-G&HHU^#J%h;?&sz!6L>18T+MMs8IR|1%FRL4+VdXAjS~f*$Zo7B2}Y?Q_!Zv zW>5NO1q3SKf1vnd6p*Gw`cDYxkg|Uahb@Q~VYH9*kM!&Z6p#o|B8yQRm>hou{EDm= z2jK^KF{>AH>2J|uT zo*g+h4O4*FDQStAjW_YmY~ErbN&qU zFDW({q(OyaJ#W30R}v#@!g)>0ZQt#FxjWL(_=@Qj)8d14@Z$2w^CMR@tNEo%ZA)`2 zO`-g4cT;p}mY-=2DWJr1ijix0ZsGMp7vgKs69iAP8s}k*(Q_n9 z;XcZo7{|kS^>|nwj)$cuLrX0vlrCjiG2>iJ0S}MkVh}sil=NxH?E9Cji(0$x`b2eU zaq>a6R*H+kMV<-sumBUDs%T2ou&1cgqY*V!H4J<47dwE4!&>negfObg3IZ1Zy8!Fg z6aiil7*%TLo-p;uC#z!Cj9~k~a)U>L39Sz*9@x5iAA^l8qyUf|fZLv~orFEbB$#dz zL=c}^F>`hr&HyGAEfToi${G{Xe#*MjI4F2>Iyy$DMqSd>)J&a8W?_LQNXcU$-U`sn zR)l^Huo)pWW;C6=8uU-i>?4l~vbjhWrU%dhv;W|yqXOVzFG|pUKcQJ#hw>`SA2~I8 z_9zZ);m{Vb^iYeUAK;XzpVDrW^JDz^pFt+lc?vpbB(F4_R~^i&zL{4WvhKQ(SGzF! zit~HUS8^}%!MxgYLcqEUM145FCYWDyGrulm+jAqoZsE~aD!*6xO7+F`V1C^>UBI>n zJb*FvT=P?B7uufs{0pUv-mg_H^IxlvSnUgK=g*w8Kv_CJ@bh=mIos}^M{RpPTusdf z>4^HR*TI_QD|!Tf=D_V$le66^T-~gvuttNFYjzWb1?Kh| z?X_wi;Tk;=VRg3eTd?0etZ0fjxCWu4I7aAoFsWpXWu0&s!16WBl_LTPI`}yGQYQF) zJAn>R>Voj-hPgu-EwLSMJuD`pF4u47VV#YZ2Ea$>R}jG$(KhpEQ8^gx;GACXI_6LJBNTXr|P+*WG4@l9^=gH ze)K90V+e#KU%x_aDWZBBL7~K;mx$|x(CWi3rF;b)j$%9ymnx};{+qIkV-z+isqs-* zKW2D0$;Qh1$z}{_Pn#!|>Hl!P!k6wzA5wKFO5KnytEQoZx*7FJJNUXQ;Z#M2Xcp5N z_+)Ryli|rwduac)>a!?goY20b4AuTRy|5-JRi<`-ol;dDVM1GFx-@-AnFtBv>2qLY zsuB`nQ3aGyrb!`&YVOa)jE*yZQC@~8qHr)U06ZSCs*LwG=?k}OX*xd9vJtS@H+2vry6%Y|03>HfYQRuBf4pqGQzY5jG-bHZWHL+@01fe9Iqs_N+C$w*D2 zuk&PjvP9_YJlUQcPcC4RXn2tNK)f6!rc^xY0=8*aQinQ~So{|{!K5W9qeru;SzRSe zOIRG!kJ{8)h=}S|%Jk?wHf7Bx!C%C2u#0vr(Q)WUEwO#{J=Xiuvnc&I&leT|}gD; zRHVr&c>n}Gm$|KDxaVNkh}Z#FVmPF%n?3>83DRfq6uJiHj4O6nd0wW~tuaqpia~d@ zZiGBS#mIJI97bl~P%UnPz7GCf9+W)Uw|oe`IyL>PM_`5+scQJ`zQwGd8TMH>%;k5q zoVi3cA~ev+EpP;6HR)px5(H+v&usCfzd|vrsW6Uzn_8UK*&~t*w2{uPPR}3{Q@|sP zGz;YvOBv}J6yaW}|DT%J_1a$8ulwMv$t(NR`8z7_76m_{;O`NThLjybOgNNwogRLM z9$J*s&rt2m`ex>`V~R$`KhZES_3b7csyoKc2+mc+HIYnF74t+vX0fEmBU@MuI?nA? zR!9$Eq@-f1i%Aooq+;LcMMv-8R>IWel`V83YebtA1KyE!EFluk%FI6KuTFAy^;PD zw^3ns7G|tE3Y(=x0=Ps0$%Sw(91i55oK}t;j?bRL!9Jp!B_dg51>F+_nL;X1CI+g^ zliE>3)Qq`B26pm6fK)c8WS3dd6m~w(kvAoGId2wYXT}#pl)bKC(^V@*1J|v0Mzv?x* zMp*x*=SH%MBKG`9MjoU;`T8&REo21+=N-!-UiTo5E#^GC{pp9#Y3^vWDFv$;`HMqK zSPG~U&jg99F3n!MH@Fh~OqVT}EK5(kstGxF zhMjFeXIseGapiQ-dEi_cH6(8}EANH&%kE3=%l()7m-oKf8Om=9WwoE{{+W@p6@-f) zxKaGTYIe!e_E2`qx&5~-#Y;G0P#rc_2XNkiq*q@{JC}Al-nXihf`~oyT<2>3NfN`-~HVBa|O|MkFQuTsx+4Q_=sqv*9FYdVMth-Wnr^uF>^?^16db=Vn ztMF%?JZE#PW^GzJx?C2@st?=h1Gf6rjM9iL1LtIV1C={qzH7I9z+pN;4~D?V*)tQT z;VorXsNJsY3eKRpD$>#(yA2dK-Y{>$;Sh7lyP}4(Gr| z-{VqgH5n~oqcdo926DEn?7L~)scv7jEr|`iqcNlaI=kT6$1WBw6rO9xq)#he%_?8k zEbm)^I&XK_wmWFseJjg(@x+A_&prZ)ameQSaN($Wd&B1X#j4dw%|=OPc(BP2Vu==X zU(X7g2dNJa^7rm!Qmg*W|2x18Ph}44_)BRIa@@E1fgIr_0kQAc21>ZU;0N-B|0*DM zmA51Ds)HXW60T-8A%4xs4|2k_ls1BX%~Cn2&_6wl|; ziXRoRc!^_RxAsR{`GHpBkGAP4zMCotXw8FmZNRFfcm_{#J4U< zI0koX16%pQR%2kBp5nVHZ;;zMtkDLyrHQ$k8v=Af`WreO#ZzrVyEHfQ_@P4mjeH%& zi|v%&%nxnT-)LblJE+Y!b~(gsUC73Zc2g)_Pw{LmAVaw>agQ#vgGc$Hodg-$%~Q%A z0Vy{%TBOYB>S?Q}!-gzxWNV~+25baRk#^4rWG-v>Ak;?U`YV~3JboIP zuKW2~Sfi^_Igc(T8ziEGU5STJ4;@Wram{iVVAXDf?+LOyr_EH(3mbNYYlN+^37R-n zHmAU#$Exp;_1xla-lzHl8T~XMjS05bs#KBL4#R88$BbJ_@^OPdlj3+=lvrF^=OX># zuik1%G!CHU`1}d}DPw^$^`e=LTUuQE7q=!S3)sWNxU%cetPXGtY_a9Jr9B`{|;m^0RHE;ifYRwDjxO{;l z2Ac!ERF#Y-&LuB*xGVt@BMj+qQpor@26mdj%ZsJZwy~L6D1=y94E!T)8D*2sOs3Xx`A1KaZJ=D& z4wv+=7~?rdj}M+iC&pb+(8eFpsbgs$f|s?7vX{u0nDiNBsLYn^6eo^E`UV9uBSL8q z6mYUJpWuVi3vRq&QAaI}I!4ItGf?aJ1atCCRJLClq)ZPW6FH;8*dbAJAdOBQjgHHF zkdt8AsNg@NfMi=tuLP$fN)bUz(sos0&f$Rlzeg zdsW1?$Q-CSc9crCu`8!^)rL>1pxfW1y8kN$pT@19PUEj*31nVCpglx74VXh%;(ikX zW~FE0U?wzjVI-Vc5zMRzRJMjPcg**L#?Tf1xM9x=_Qi*9=G0v2JZAwlZOmG%`_A4_ z?hg5wR3vlo=7SFh21f!TM+5dT7=F=_DfxJ2Q+lnKNWan5A6be?21&gOwJg@e^WKE|B@t$39 zWg?h57!U?Od>=2#oJBgn!bncRS1hFSE4=q!8JE9&15!ooG;T0@{RO!9mcD*UkJP@=D28@I7FGw(~2#}c?*3>od?(2=$O9RdQ z@bMNf4lwvZ5Pg4+Y4c?Mehq)w(bdTPNn1Ih*YeE!a=2^dnfr{|YrFV;g8tg>wp`r2 zp3U#m>tA=KQhbvUK(E)@km2<`sohTQjg$(+-)J=N)*}8U$0PnG&*B1)TyN@G zXf*B1GQ63gMG0?a@&uX1;yFd#nc6qEJG*V#w`@AZ-^!%CZ#hlfd$n(sIJH!M3j6bKNQyzd#VVgVGLtb za(D!`LF(e@-=iM)#0IL1IhfNUrjbdI1jxkWGYM^ke?*NBN44S}I1c@m((d)d2l}D< z;y83%fkJAWG*W8;X~QK`Sj3&ldIXOaM^H`iO_-(Ns2**@*j!t&dD)b{t6OJWF__SI zY@I5>21wdEQxa5&36OE~BszRZ2aTHGwwK67Y@I1>DefaeI=Lm14*RX?rrCRTuPX4{E$60u4PJ2fi72oq-h6!<7mcLS-KZW*R$ zJ4zc>!>~UY6iAO!K!TOZRLM{AyY!s3h3!%*N|$EoDIx5rK{>9t~Vo8A9Lp07vK>#-cj=!uFd&@$IzqkH@&T?752%1?}bYeYdPx>oD7GOa9`a zprxK%v>J88{HmpJ@l43l5;nF3j4dk0v&Avqtpto0F^Tu< z8HtkAquh7Pxs9&Le)UvZ!N|_BErk!=;6bRElrX;^|y2a{)|P!(abd zSuXu}y?kLqi7+@2l*{u$;=^f*igkc_v5Mqa~xq2JsTop|2;kVKiJ`l>L3xF zlIGIfkXz$JERbQt0_dK`WReLzjl{&|{it6`K>%L}4^SMXanf&4EI_e*dW26rkRaU4 zh(+0#sG@aXE@D9bLH;@lqo(K<`Q5M4JIF%xxaMJ9p4Vs{egTy+32MZW5wT{za3o|U ziP#G>m*<|JTi&)Z5UA=56?Fv)_gxteWc7w@y%AgHs>8LE6?9ab>xwvXUueF(Q5H6r1bgC zu8m9EBwN`%)z;F^^H;a?ZF_~Qtp>#JrIRzhde-b(xqmO?B+@C^C6123hRX|NNoW~o zuNZcE5@W_j=`%4qs+rm-1-I`b!Z} zFv~yefXSiYFrN46@n?XVm!|i+3qF-%ZGwU8Nz_KpPD$jl?uxtcSL&r*ZUb0VoT9*v zqeL=OVuAtv0pOYqa;okj)ST$~f&LBQ)%B;qj}rVbz#C$<84jG173qhL*P>5wvnnK8 z%b*wHPE|=bqzZUIA;d-q!Xb!1&=1+B5pNi^$Z@=5(daV|xY*OL0)MQ3nu4e43|@Jv zltXz+q_3I(=)4!bB%5@V)~<~)N}MO5)NZVXSx))0p*0>5s@UkU!qP(*7!${dZg z5=%9i@c<}22>Rp;^c&g^s!}y1NG6UF)3A@kRwW&%jjN#Ybp_IMY=%Psg8`Dpreg3A;N#c z1E%?u4q^pKpF$8d#Ty?@^*)9JxsX=im}OKCS0Fg4E8zfEg0@q72)D3^C;Uyeuow%8 z%%~+IV&sEp4sVuAw~t` ziUzO`G98sJqpToi06R&c>(P8 z<)TYP%XXX)4CU0WjK4DVy{RkS@YcT2);<;LZVFe>2#s24`HR&HR?=HpFIg9KKW=Ct z!NNDIUTs{k-m+&eHa^?BgM%E<5Mq#D$4xPlj#90b6mR{uTP2cR{2w!; zC9aKXzq0RbV=qZ2`ao)5#!PvV@7~IPF|FIiUEuds3eO0LU2NCy+rnKdc2L;bz5_pR zH1Ydtgg2VA5q~q4-&ZHRX%;AMvm?WsrTo4I;mtD2{ALA}_+}+f@fx0B>R8yoQnom{ z)3k5t`R)|sTLwMF)3ktm%jW8C(!SN8LEg8v@Dy*-B0eY7W9Gs7!~z2W_8q(t_DPF1 z1P5*I*h={KaxpOzzXAnDIEJVIpc+2wDC>!P(!lQJqiK^iNhyzvl-v)lJ^068}yhipzAnep0vO4-vbFXyi4i( z86*A9SJBLS7LV*6I)+;r->i^}ivXxJ$qX{1A6HP3ENx{RF=#U!%ZcMNzsw^OUXU8_ z4EFzU?ewV3Ex%29nG}(n?l#^)U3rZ(x$KIf~i{r!d7NOw1zz-{^u#0R3s*lIk`_j!}82sNbJ?11oa{p zWX&5NyrP48A2A8n(;1+WwKy#!_%)57>p{OlZZXv2W;nhEA`h0ep^lSVYr z)ak1>OJ!@<*Fc|80&VdK{s!^4BX$}^U>`=pN0B~3X&X=+F~)iB5mSOR#3gq1!P5nE z!WY}=V;=9;Kj#1yIp(NJY*CJBSUb?q!^utC6BxTll2kY zkHhB#+kU}vviVLME%xPE3GPYRhKuv=ED;DoV^A;WY|kF)hz`6RUu zWo`~zH=h%JQqusZmw(u~BrZI7A#2gT_~4~17y-Q88MHRuve=$JasI?t9+Axr?iO$+ z=UoBifP1u+|NQT*jXyCuKRkC-1-fB${+pjvDNkD3H{;`aZ99bv0%E_H)o$dz!?*1g z{x}=4s{-RSbw-M(+1q*Hs!K!h5<7sew(#wG{nf2{itpwreJ_RAcpZg$9_iPNj`mXR zwS2z4$at+lPw`SM((xU?)9fRXYcM!XOt#qCF==9p4N3Clkp&uEG6DHUF#PRG}+KN%GaSSbb7N@JB!x1*uUxH9s?Gz zQC+|mVnE}}PTbQO7BNJzsmSF^@fi<2M_!`fDJ@+oJIG0LF)l?;Bu` zvXoNdzsi0$Y6Jt68vFVic2O|}OduFN?3=P)9^T24u{zL;!p|D>LYJ$QI;64~N(z0! zE`iN(#nY9TGOk0VN=p1!dD96eT2u$l=p&D>z6o?vMogoYxblz ztEX|-*9sQWIz$`tx=Sf1DSYcXVBCHLyg*w4n9h^2O~A-DfwR~a9>8A}{v6m4tjRoz zttoYb4FN1D*l+m)3yN6-nkGJzcPr20zoLCD3+h1&75f0Ty*Ok_NBRREw8u9n_`k>< zHIapg*u zaEf>o4DIx&pWL)$(n(^t9l|FwVeAP9)8n&aNVclmh0M~C$%XjT@6HYrvQX+i$sN^FnNxem=U%74U;*_$^^>A6kDc3 z{*+=Wvo2kvv^Yb~cyXId8~h19Qi)pn-j#oLaweLtzFCzJC?m*kQNhn6h}slKKMFw9 zOyjQHN&k_+zlR`dioHZSH=7oV%Tt4i0Q}exIC__fmrRoAWoYRtimAjN>9+xOGi{QI zvpQo+`d^ZwzMbY=bcWJO=J(yo+7!;JU(SNR^1`N-fly&**x4BnGH>Tq!W;LB8J55K zdKeYYx6MC#uK3)8u#;?T4Q03fN8@ti3#m(OU$=bE2nXW$hTbiEY0$oHzVCK^(ULio zUmFl|Rt3|SdcV*cF*Zkx1<*OYZ@`CL!N1#cE>>Qw47gfeO%J(toPY2NkA|c>7k35I z%jew@A?Ao^ad;U`flH#s+xU* ziM)lNEz% zfx7Ry{%?!}us}s;bv%0TZ+@EWPqr28FM^JZ?>2$B%cl6Y%)y`6 ztxI=?@J5b6@xqqwT<%Rf-|Z0IbYxS!n(xjM-mDQQ-e3pJTUx%`DZC{R{98sX%6ThI zM_~p}DGnZxZ)LF%B_k!*(cPkbtB&t(G`>}@r+AB&(zm zL;iw!EuFU5H70K=I5Yvllp=Lgdivn*Y#~{#I;bB$L0#X0c$_^mX>-3K5QwvDY??D^W*7upg%@Fs|~ zhLWu)sP^8hE$aa9z!zWI-}@d8c=CYy01$AfiTy133F+&!{TcT zvoqkNWJNgQ^r;5fpo=kAggfV~C?bSnBjt?UKBhsZQD(bfoDDsDOTf>hD=m840 zQ$S>bjJu~0!#7b`4aBHNO|j!dXi*$pOMgk_zd_GaXaX_gD?d#f1((WrG))%kbBrnei>0n-1z3u zh9@@3N6{F#pSVpjd9h|5Xtl5>j(J>{71WTcsl3}qlt`iubtR$gu}fLq_fy1BJL)_F z#3-+Tx6;EL#Q3CO!^0@oLN3Md0Xk#}sSW>h&orB38h*BhWy2->4h_8o1U z^~WTmD}m!=wD1P-s?Bi%Jl>BBzN|OMp?ZkEFNbA=9O}8;`*N5)7^9^9NOm?|ZdbaP z(VBpx4nTcEd~Krq5?IRa!FMl32XK`!?nO4v55v3&Dqs2Xq==bfmYB_Ei?a#l67clQ zOqjr}Koo(nk%g(?QT~+)tv+sEY`~*1t|GUeF%#gHGq*oUv^G4GAvBH&R3_txNCtoD2M4%r!fEj+%mV!(sU zZH8mY(XkWcXLctzAlXcI_aJ?xZxDyyW@_7J$0ufL?+JA%;$>~r5Hm(*Sex1ER+^cR zP1-~?GmEM+iZODU_+a^9U=77c6eZEZl!zsjNRkc2&yYBi!LbhjLG&hXAB2_Evv+-T*gk2`j6PLR$buaaV^6SF+%_}8$(+uPs zDh1A=QlYDQ-^HaD&`yK>CZjNtj{kOL%YnsM=~8RRRy*HwJJlYsko?s`YFk@@>KUEd*O-zk{pZzbH=sy0%5vT)_e~nnh zwlHQ@G2ch*oI32Bs`&?AL+sjk=k#dD$Yey__u^);Xp^wvGlA$)P^LQaWXBW@f&w{#0&Sre^6wER$U!6{Ww-T%OCmGduGdRbtjaP4n4Xd}k3ab~f-?OxD z!Rl?V5x&)?L-cAD-(DwNtrjR=ZwJIR4d31%T+>!iyoN{mwK^6y@RYK}(UGQoP0x3v z7+*8!DW0Z9`dDmwvLB2oUHp4h2%qDQa1!i_B`A3%?Iupt9D!Dh@fPsNYpe!x@!7_(@4}jGV&;n(a?z#+*wC3AtXQhFs^S(P8jvUB$7!1{Yc0h z86g|Ri806&;Y#`_Y(wAtg_$ zaTm3RT&RiaUw1WcXmG_$1c`lHcg5W6^${PQOtC5g1AT-#mU#A$!#@~u*`Ug-atTac zpyEQ};6($o5s0V3$X-rEyHw5~-uR71jX3^f)b_^k_?9^-Y2!t!F)=k}=1=G7{s4a@ z$sfaCGyV#Jdq2LWkHWo>5s5{fP=87awQoj2|3|OfwAxGamaxdL%#)z2&}rDvgb_vQtU_0?2VpHmX}0 zIpGW({Hi$r;PUAFz6A2vnMA3*lpQW^4HmZs%sbX-w{ZX}gN!&9NAZ=pTRC}`b1&sa zY+GN=eKifI{d4KGzr%u0W$U@z!pjAh3cgkpwpaX(y<#=1bVVE3H4w}i4A=%`+B=fn zfx}-!a%&+_g@8N#>520bUpW~z=3pyax8Ko*mLJJ-MRJS7xedYGhJ>RF+f=oA7HYq% zhYNIARi)%6+uwd0uj$Fs&TRf$d`GSDZ2_@AD(-0IuA2Fddf{qX6~(vl9gV`(?b(Q5 zv+^BH!Zlk1;;(UhM~mBs7KV7zg}6v$v23P^ZMJ??`?;&J)3 z(_ZDwto%-0I6yl~hk4=e7zoByPT##KuW^+t*%60(pz>H55Qj{+Xdah4q47qFHswXbx)(*M2wkfkGR>o z8TsF}yle@kZVL$8?q)Mmx9P5lOX=j3;eumm_LCJI8vYOY_I%-w1jJS>?Pc75@ z)gpZ^a~-29>-mziss9(l`tQP~CbclbS-+1WwSWPVol20c+v;WOHUqP6V**+Dcm^Yz z%E25)2jWlr%aCb4C_<8*G1wRnc=Y#)rtwijoSsre({b@`T%B_0pn~rdkuJj;uK-(o z3xA(oV@ubEl&y^6Rf$B*s@S!rWgmq@k~6$>KUzZysq{_Up#CEb02q122p4c6Yf57D zkvs*HTN-?%Io`lfXV;Lc{WoO00NHTD)!8L>tf`}~=RnW!I$};HDk~s=l`B_G3S=vF zP*`WZbgoSHjaCjKsu&}gAc$m?6iH5{|4jj5GcxHPn~@q2{r}qg7U-s~Gu?BfV_BAE z$$DCrU-J7cJAME*U@*1;^N;~ya3PIruz3XxIf4+}OvfcNw@y2=VnQc4P49}6wi9P{ zX3DI2gn8UMU_(mNMpy$% zvtMVQz5o61{{aR-;@&=_1gQi70Rpf&51%+P>|qQ?4@P8UlOIv2($Fqns+9OKvMQlz z?E|Emv&T383-@pb;pLVtRZS16ioiR}KhDEgzjX3n(ahOQ{t)c8AS`e|K9GuQwfYGMO4 zgALG;1vWs3M)+1WuuGYIhgP^`FcMy@>(B|893qJ!B1{)l z0iV(E2%iD51*lUIw1BAMXR^#44a%8nzQd`WsZkN$phP%Mj1xF~2{R_qG0zaspaO3! zGGii0x*}qSJm!ojRU*gDKPqO&w^6z%CKia}4H_%#O8AphO=8QnP%iSy6rB7^7Z^uS;i} zF#2^R0dDrr! z@7!Kbd}qRqCIo};+};4ba~k3GJcT##gg2Sj=#?;=wI*FXou(pOuS7V+UtvvucrfvR zmHOlv8i#bIJ$L1Xs2e(jF3`1nf&){GP4;djO>;V7h*E<1DOnmVe}-lc3XjJAa0^3D2k6`4L0i-jhSM zFtI3cxUhTQizLx2+AB*CG`CmczNN%IVO|t4&gDMol>3BQ{^W_nq!zsA;NgSEPeEZU zR&tWgIXZmexC64`5XhtMaiA7Zt7ZK{FGSX)MH?XC5X&jnIZAZSXQ*?CA0l^7ncq+z zNH34;nu?F^v98f&W3tQ=&ZK7ZBbjxBLuAl&Xn5}_^w7S2|DvAZ5=Kix&xpOK6IxY- zA5npddg9t-ixrNhL8~tMZK{y+bJQf9OiSOUp(M$>76qhBsB&ZyG4({*?SQ0w8mv&u}d0WSUQ^yVr4SSq0l&c|+fRgzi;>wUy zzG2Bg>R+HDA^m>lFPvOjuyQ0@w7O#=jE=_q-k?7v^G6}Aq>3j|ZfhkTwHD4b@Bk`7 zDu^_FxnQi~J-kx!=LGH$_D24Ok^dIJAIVf1($lUYp>1gP^os}T7m z?naIys7{9|&0uCj(7c-&6dmmuci*EU0*KV**}?35U?uMe z`BL7LD-{@P1JRb!1O_$gIAajoP~6uqd}cDs zmOiFXI&o$^{ZPXl8pN*SuaTw16d}zb_L0%qVMytLV^H>`Ra2qtJEX4_5hrWDhA}dhTI?y0zE77Q z)LQYmr>j0>pE?7X#97VB59By~InLV!PDm6;?sS6o5^z~`C@u3>Whk8s95#Lem zb!*^u5DXGC7$n8OlYcx{RvDe2Kq)Fx3p_mR|H zz;RTPRU)Xkq|zGkKpQA@n4^K{7YuC1r6q}zITsX41SrMuJlBm=$%Ivzj+&zThS+0$ z%;`ysa_+|k9)@ZgkqdJ!x32YMgku{$RwBcSq`or5Cl}R{X())UMU;^u{1(tFN5_)Y zD(3V;=Gro=6Ceh9)!@v)Za_Nc0NDuho1EJu0K;#bvB_@S$G1MBW%xUw*M<|6OLP$@ z+}tN|TE0KxupEjc(Zj=JVysXuF+#vEEylBPo86X(B`VfWNi9)XT_d>%jvqhfX{{sa zNvE`4JE2}tcU0W(sT*lBt~mQ}Z=b^95@#yMPX~COfV+ zP1wEJTLPvnW1Vmj7SI>K)Zk>JUtbnE$lB?%_IWe=a9V$^`%L%QjbmLm3QGK@wy&L- zDEv;>>s{xo0yQhowZC}6XKK6Pxv21(IzCM2%u7&`-_(4=wB;ihr8cjU!w~E>HUA1K z2wFoRJS4}G#2ye>nXi&`$olVNi zO(}%8C=t6iO8B7C7W^~f>K7nhr1hbfn?Jvj>RzuZI$J?F1al!q#oSAt3!^EK|AQ=a z$EfAE;Sip)|+}bI(tD3KRS!c&ybKsZ^|M<3uM2@5KAp%3)U#D zH!a%Qb?BEgNq^j}WS$Az(0&rfWLMu4u!2M+Ij*@aV?CUjOyP8)k{xVKh?yXszbe4~ zSbW)0V+ZrgMo(F!dd0(qOWtjj)AjO9JwvlQI~o!d!3L*I6H(tt2c}{vwQ(t<>ZaiD z<;c(X6_aSka7N7OTi;CxeXHlM;>iCuKPHI@$F9D=l{XLnU&<|%%k7Q!(y%UOPB~^4 zIhk`fs0GV|5=ywe8WNgc+{NFpemHa3Fl-z)?Ixm{UA-~aTjrSQ>1|FqKK8qf1d``m zr0yd3STeU=^5}AFnVUZgzHB*&`xlg!;nvBeMK^EOUW?ofgAjM$O^iWy$)N+F0e6$0 zB)j0k%PQxAeu%2f#`ukxQDpa;qVE6fz4mRZBbmkh z%vB|T^XX*oAgX#WoI_MB-2Wv>)tl3s6K!|yrz$V%&MmHfd%)%6K2@&KaTu5D(odbM zSso{Gxnex9x~_@q>IgcVhc0XS+U@R< z!**|xv295NTVlpXygqZF@|{V!zn=Ce1-^35})&-2Y`qOMw-?-2-x-P&~?(zxnyI z8s?yqUK>=o@RgmwI~*<@F6%A%*X{kj?Y*VFWld2&f`i)(?G@Vj_LfBJOnc(gZF}Wf z$9~&xL$(FREldCgEb6h0SYcyi3hyR zpr64@4E_jHzrz22#Xs_Jc|J`|P&&qobR}cJqDG(;9o+80XsXCQT)wAYw;gA{k$lKHqU&#V zACTjb1Gd2lIndU9&gIUD&tZX;H=#F&X>SovFXtxE>IrEvMj!fJF$Ww4pQdl)-W^ZU zE;SsZ`4eg;e>sCZjv5rycMkl?ydB08&U1ZS5fNgENWw8e6x#UHA%CKv?&r|2q11sr z;KM+mjv3k}w}9eP$e-Ck6Hfud5m*2T!995gKMWVwQHpN5UC%HA8whM9KpaQ$CV|@o zen#Lu0-GrIC}Ff}jyw@>l_Tyz4aTA#kC?TLRGb;SiuYt!1zjbDOl=poP-Q=qz^_M5nd26NhHVZQOASmKDZJTU1&n(67g+ngqMJi zKp{XVZQ$Th&l5N|9SSM89X%OJha__UKDd*I-+0M{29v1423tD$m*fIg)9nWYs;PI@ z5Vn%AOv0cF#)-5WpW}xxj-f=V{9-SGr|AjM!twqGFVpSM33L&-L*VZT{DJ_J^GRZg zGftE8`;@MavS$pc&k&YJVJ{H&+l2iEVSh;&vjS31O#CB~oX{+V-X*}4E;X_DDGekh za{VEm52Z5Z6!aL`C_6z}l6t&2LfB^s&?+kpD_T!uZV4szqR&y10Rnpn{C7%1?DCk? zL6N?jWDW<#A&S^XU_U@ejj`B|m*;^8Pk0?1mVTb}lp{~aC%YoT8_WeE4W@pwwl4Wb zX~f+38op}}ommj;?L#A%Y5KA!tY}xH75+q@GpP+Y8hwt&fTP*xXuf653s`HeS#bp6 z^kzCEuF%L4S_T}U6;{2L_e$Ql;;Xt~Za#Uo3!2+5+Aor%cpj`}Dzc2@O2~B=mb|8Y zMf(k1!0sGR{RQmcpPYQkS5SYx+h4HqmL1xc7t;LpH39P)uXznDa=_pwOt8uR)V!IF z4@*(S=R9Mc`wlLvVoGs-gWtL;kh#j6xhmfC8dBb|7EG4;tQBM68(POtUe^``bBZQ+ z`Eu&UHU+ctVfs6(d<-TCt>aH!*CKVvWS`Hv6se6xlS?l??K5rWC6nvFkpc4~ccc!E_*q=z&s`PB?egVz`E%D@UE<5#I<}d7 z(-qvZ7kpFkn&uVFYr0o-Q@?#t@H@Kv_O;`wAP&rhfzr-vrJc8O$|j#WpXJYK8CTyi zmQEh<8|wnvIt|YAg|Gv*9d=jc`L`)1`BGYhP?Srf&68@{ACxc7p?yM_2cVqXWNPM8py7k6azM= z&*q#O@!MMAoH4sNiCe@n)DF%Wvx{%fcbQn>&sh%VjM>E)T8Lfx;?i&$XUWB}c2n~k zt#7nW@~i{&uj(&0jdur^ulT#xziXW=e68x0szA}wYeh@1HjQtj`X71mNFaa7)Dz!1 z`TEK6bwNiVJr{7)Pwfd5H2Vsg&mZ&`tPT{cy|`w4JxtBM{Pc@YPp$PAG~%wP>b06z zYNk&4i(1Av-UgF+AMyXT0jkGZOQyR=I59T^p0;DNn zx6C(WaLLj)4!vWt?+&w76CS&Iqybn5O$_-FYYC8{qgf&D(eJ4^XoTVIg^0&F|V zRS;k!+*G||mEuwxzoSZZX;l{RX)C{@RyCbfOn58516=>*0`M8mg!D5een+Ef#!S!7 z*wT^jOfkP>i7-==LU^T~QZD0nw5VoU*i$R%smq!CjuooQhIS=xu9*3qd8#XxHO093 z?lOL-L-pO3b`@^E$5rkuR(!9r2KZYRe&-U^Th?;ISMobsRByFWq2E`oU)d)pu5P;2D zJ575tRYiD)fRr~46noQ5ZM>PK+U-oenaA&TXm93g39q67l%P69QX$WfJkI}7%f~H5 z2uoM8qxTrof-JnmoG(d^#TVGtDCJF}7B-Zbo@i7oRxS%$ti?^~l5MB-SZW!QIka=P z(XjlIB5yqgu#G1dPPF5W9D~$@^#F24x{@gmzNKh+Q1{5pG-3JfU`098D5M zQww_-SwRJnJLoY{DY%7YQQ3O(K|En%uIHxQc z6-C?`EsDACgR$&zpwsu$^#?4UQ3fCXl)3S)TgZne1}xwRgT$WYT~KWUT>5G92UGST zMqvV*v6Yox$esfp0WhQ+9NISkQ?mVg#DU?x2N)|cltx+ZA%Re_i?}xi zap?F7ak!tn@E#+Z!Jh3f5}Y0hJqelfk%35M7)vym!j126|RSYA2Oym)(K4<$6X(#n{Mp_HyCVO%U^=-IZezoXsN(Y2|+ zYe$!ByPI(uW6VUz;~qUGnGHKfZBKKtDgBM_r$b`}oS%8c!h&r&$SRG(^7{y220=)6`bpq)L$1YJ9nt?w_*Nr!hKXGQs#Ap1bvRm4`fVR-5Eu5^H8oaJ;3~I?A4U8+q zc^U|(Q^28T4+XVrF7CRx85}u1<(>vF!33aey_hkEI~qeklXFdzGtutX6}frn(a| z$D5LtcgvU?%rs7HnyNlOam7los*?r`XiC}=(zv=oeQ2Frz*DoSFi z5MWG|cppPZE^|eFnU34qtbjJ(r_Co$raC*I&hx?h@u8`b>*~6=fd->hpM0SGB4MCK zj3);(*Ili;nm6}tp^b?KlH=@G`P8<%`xQ{&yDMPaf$5_!hB9he_T6+0KHR3+-<#+6 z;^Vtkm`}z789FeHGV*>oUKAY+*R;7G-_6BaxC^g~?fiR&g1Z8`2q`DlcvrRX9Rb*%TYKPiQNz1n z7ArlA@FJM56fPB40iV{HkYu`$cQpyqMJa@rQ~K$ZSX_naHcAgASXZksV-*OuQ+kMD zx>gD^&h{MKTuI}5@>N$fRMr)}v8P;drI7C_R9z_&2rr`tuC(wyC8{f}Yf^D@RZ+gB znY&uaZ%GlZR+Rw%p0SqTDt?Pvc#AUtf6KscNf+KS3WR4_w`6f|)$&_(!duQP;NMT- zw-|))s|3O|CZzd(9>2vbd_Uhpcw;e2`GJ++nkoDsi>md5T#9)D&@E42|p@R5$@#C2S0j*!6u$U zmNLAR!Ic#9HfQe1QNC^Fd$QDTTU3PSP#xYzr<5z+E@5fP&07>HZ!hP2+B9#kPyzQU zlt|-6yP7$#n!$7ibv)YWMQ;F_SxA<7OM}u|&2Mq4y)`Pr8U>=q3D==?uRr(tFtxMFtCKchWR65j|A%f0)VtYvXJb_0;d)#M;NApdL zFi9jfZkL9zxfgcvpuLkFb|G^F@LC{!hcROUfZ@c`)VD`ox6Vrr; zF;G2pMcsn`C8+0<_PBW9aL9O zxxcgf_1%HmHeYRM2;rW$Sk01gT=B2L(PeYzc9^^TxE^SP7Ho*aMT zDieAMS5mL6=9Bj38Uq!+t2NKe%IMY{e~haoy7>)RL~WqvqmK5 z1`th?FeO$C5{Af3C6So~)QgjuQSWbGAD%jNU08kFUh;$Dt3%(f@}>8Bg&hf;wayA?tF?2unQq`atAy!Bf$(KnoekU!lo)D+nM{V4 z^PNs%rb3|b8WYlx(nGy)nX3eTxr!%P!z1=GR3!lGSxAGqD?@oj#doEtucWF7&rl-e zNKxXvU-$XQAZ11zrA-^j14J+4D6_SIsEx>45RFgxKI3MWVYshbTnMU{KaBL!g_$K8 z-+FOw4pxPS;?Bn#2-AQUA6+lvR*~fOSlsLo)k810B=);t&yY>|hvUZyFkzJ9QAAYg zAh-gLU?#aFaHW_zBm6QBIer;`9K!BODAGaDjKm3tRd~iCKhp=F55z-`RRWc0}8@|MPki=<7h(`PzS|~ma?Dfcm$^vLZ4vqFgimamft(rg$ z0X91_*&{ZUzD}8t=@GGs5;p)~ij2;LBHPQ?;lYraDolbf(oUX=m_JYI$m#`ER%(Yx zM_u2c*)j)3u-P&;W$9skB)f>L7LF-?Pq*l<2!^@gB=#j;z*r_5E}QD`F5BocZt`k3 z!7RA6bKioAZ;Uo>v~|bShpoGsv**2>`(o}(`2kb;JgdI5IIy6Q=T6HR^)t(p-^be{)wrP9J$D(DA2ropv4ufU02t2-pkN9ZR`O zP$#PuE-A7I&*sTyqMgF0Q^;oGw2H!~^O()Ve1Y&H6CRz0CB$Z7dWo9ww){?uFq4}J z9J?H(A%F`m$C#Or4> zY7j#}no)b>PC?jt1-|kab==F**SeVTAn@2J3A^0U(-oX|d=s-pf(gY1=cR>DN#R3d zVP!F*3nM3-pqUonh(F*2vL|PO=N6rLQSw6>ZF4{^yN@T^0C+As`^&u+GsHBPCU-#j znAD4D9wpwo>m2^UU3Wc(|4#gq@kTS_1wSa)1?EcdrlYnJAli!;KPRX9oJbD~?E>7|Bd?g! znp+Fk?E)#dyLF@#E~VPX%Up9tk?UO|v86{gFV5ke!$^)ZuN#@~3&Hk|v4P>h{HVBh zXymj13an|ymL}Uc#83xQI?7zH9C7_CWRH5wnKRzLZ03PhkdQ#mr$Z`un|OR^@E_6d zkwXC5cN`>ZmLpl6u(U+3;2e~QDN8d|X|`0to%?cRBW4rGpf#$HvY12PGg;HWpa>@X z%H(i!DKV{YFu29mvR=xfkV2V|>F-igTI|DvZ_@%s!nd@5-A*-J zx0Lx)%WkQ&C#rnv(tx_sr>?xNuD)ZkO&DHSHkKMzZN3osYWwuz-@ zSCQNoJUIt#Cxh9=6B#kFuURJhrGcp!DgMBmco6uI4TD%%f*mnJ=^KCz&=_7{x-}y4AbQ?cKWFyR6q|+~L*k_!Ws_ z?SyMJdIzWLyQ=s<%ji@nF2UfhO?8PE2v=DQY+{@M$cC z`KoDq7U5XP9IELCHQ}u$JUf%YcND2-bd=AGQHAVbIGAudk2EvL6A)JN038e#vDgxG z$Ep;_WOXdpK<}FHRZ2W{S)u7FPr0mJ(N&akMa_4mtFNS`5Uxu_&Q~lMgcqeC&6RQ` zLLRjFz=_L|gv|#nYHX=o0_3siV%fyIc{_JV4HGyRd(zd*$5<{z(vzcdHW3M~y>c4* zrPbG@0B@^I$#HGGwEkkZ63uAA6_R|nV$4fdGG13N7q_S3lJ9A@r!Ksf4#N!FL+xqe zIb)DGaw~Szm(8wZR&y}lGh-(*KL+MUQyP^^g(tZ`;F(dlaD3P4pTDCf4j6oqi$}L=AO^$lkn%ff&oPuxO0T1_d8s5!d4udLC zAm79PckoaCPo4oa^5AzlXSJ7C@5s(ga?rkcYLGlJXAc5;zL!iv4@!^Za3y!fkhTR1KONJbfVn9e~7I6f!+53EpX4_ zpv z(=EU$S~!EtE09zd`s9U@bQf?G2i*lVS5*31?kl<9fbpcdadrHvhQm*=rtC$qo^R1X z4%uEb-j1DGfn!qr;^V=R(#c_8Ni(F(=@zb1Y1u;>6qkYHJvBiPUirtd)^`@6wzt%dr-=E|3+Uur{dyP$gZ4=zWjz8toJG>=p{ra`ROv@c( z_PN^UYbTX{V@W_;;?Ts9R7mvXCsPp6uYWZKSml?c;0 zDTF($6uyM-DifxcQu-Mk-&G;Z=mo+}lzyg+@2V1J%GHF|Wv|r;m(}g*xVd8D*Xo5U zW{S9y&*K4{gApv@DFlxJuVAo>h18hWrYhg$__a#)o4ktf)HI|Ne}}%D-zD%R0BDaf zqKt;A2P2sKT*Nq}Vcw7xA4U9q8lJ?JXT$S-p(3U9K4?8{-i!VIJg0ZL%a`8c6?#4r zpGAfv#fxoLJtvMF84yn;EWgonAQje1X{G!G>*T*j-d}Rg-l98OI(K@QeIl(f5~r>n z?+$x!@ZOI0x|Ot&EXE-O$4;O${ff;wF>c)gJkdpW#1C8J1im76ptMhnauZpduv=DO zb`yVNnapF1({H-le~pJ)Cy0=YDprD!8ELR_wY|V z%*!#pBksP8|KkZE1)qke$=1RQ*U!iplqO!{Cc?K&2&4Fpo?h4XimFNnbSIC)DbwMa zc}d*8n=3ln-CZQ#C8Ry_o1WiaDF(L0v4-aHovYA_h4!eKL(WbkV;ZleCz`7ol`u+`u%uHZ(pe>P<168{RDyG3*Su$`9OuJIE+=)nuZ8t2H;S??{3p^XU1{7v-?bJ?RQFEZE^@1T-R)0RnSGF=xw&tQCh?oPu;-!J& z6&D@}mevKUTi{AGQ-fYelrOw{guH~_RiQ&>t(>)LR)E8SSu>Yi`%%c!Gb^Buv$+WW z=q^=j)(+XPUykjGN+@1a+kWl&5mvR+y0tt`Ai7d<0s-uW>aJosf#`zIdqo!E*?d<9 zP9W@rH}PG1;Zie&Piy%ugD{<;Cfwrafo@bJQ-(l&J`>v2^7M zm-$-Ymz^dQbp@qBz)GPaT*D*$3Z4bjGiYGYM9*FUbqkoALa>mh^u^|`HszJ2d{?Xb z$}$zD2ZiPI)8dNWTdB zV;lZgB%;|EGFU+jpSkv|Gv&2Zp2EZ@@SQwHi|IKOHfJ#{NWPd_v5q;V zb?lLBTF0{4I@ZL&NA{y<9zFY5Kwms2+$b!C5!P4D6YXCW#<#wZ`ij}7FN^mQ0@32x zc3OO9RmfnriOVecD5Nh-a?tG|e%EQ;nw3~!){vuWU@ug!Dd4cckWFhv7U9|a8Y>nU zHQ_~;HE>(KR6%%)3CXd{tjSYNXQ&7_^9Y9yHbGko!91SA3(RX;mD7#+l(yAQZA1u)7H!xXcQdwvTqiIR;+Yj`NRi^sNXmH7Ax6JoOy&YHYFM^4 zI8&g6oV>lp-)wv1guYVcBb*qKGMvudg9QV1&b82i7KBD1W+GjELf^NWL2}SFtaY1` ziXfZqI5sgw6+7iREGBjgrC~bKX4e8r!;VNamrVUPT1KgyD)t+*wm7VFTYEEb^pe%9 z1GF`!4C{B7%PD)c?rgUm*3k5b%WYKF^X@!J>| zZ10JaMouDRCkgdFhE-AQCqU9wY>O_E0e)%6PSQ%^AOZYyaVI!KxF2{0AYmo{Ps)=B zE(gdrP@V}r^Pkiuu`bL14X8`88zVP?V8PE*zsF9w(1@JS50LzS9j z-8eeQrH^AX&Ru$WoSmmQxXqZJ(mBY7@zVYb=QzJl&ewk+_9rf}AW z_`U3KM|Oyowt~(ZpE@SCg(?S+o*X7`+w5?1;KU$QK0$iL&L^Wu-y@nKk3I+EJ&myZhXb#l!g`|@h|BST7|_ofWhYCad98Hix5d} zpfYpret@KKrHSov!VVK)=fOt^8wMc%=>nczwC5dz`|e`UjQvi~F8A02XG75vsMBw&$My^3z{m|MI36H$fgO zpl|T$8_u`-^=+f;gNCxn-_fYAunz_HLiL7p+J|=IP+gHlcsAdSLv?#S@M#U-jpOum z3ZE|GyUT^?Vmsk0`EDq*v{5(|O1o>JP^u<8-_cz!%(U5nU$*ky&BEm@N(04}?q$N| zIu+r~mhR=k6-~QTg*22OZWPl?_v<2NXTH_Iu}o3)k=g79610{C|Y6YBZhEPg|p z@Lk&)s`U5rc|?4#kiilL%NeX>E;b(%C`#m z4Mpm=94f-gln5WGSZv3<=kS0Bv*^I!!4sa4Hl7|!SdcJNNIU2TEKUD$chC>qJtyBQ zJ@_E|VRp011$JZ)-kCo{DcFGu9>pWl;XcF&k9JBTWd{>Ii9h5~c0H7HnAt0^rPsG% z_eEx}n21`u*{i-6mgC!mH5Br$0$*VynAUfYwm5x%$YQX*_I-;;lVS2RqJ_CCN^yu% zz|d3@al5{+xig#!d4B~GX0=4Z#MFeKk{R6}BAdu1?AM6 z(J%1t#s3@leWD|3HrT{@vs*1eLo~pN;rP%*ic9lJ8)$-tU>LnJV#GJ zUO%KdJ^+uR$H^=3FeKk$GMHJfexM{^jBKQ%SjjSraG0Fr4xxNJp2!&vr3?-3hr`2f zQ>FB=>h!}vu_vTqMdP3tUfQ1}qEMjJ%!D^A#5}&A$aHpS%sdRo(S4DVTE<;wvwOcZ zyNhR#%RdvzcNPWCA^EZrD82}><}K*Ds=4@Eesga?*9(Iru%05blz*%*mSn=0MJT-} zk+>_F@>Z7p+~G5a&khGNOU6>7q+kk&9<4<5=+#8@s2R~?s+8JG-)#Yt< z`RzT|T3zQGC%$kU-WFS3V|uT>XCBpa+h7`n-50W{l4%(e;DBu0$f;BSbXWLyuzxxZs`D*)9>Iz`r=uYq%meF zomHT1vqo-t5C73MdrxwTW(K1KN4HsYbX!PAw^?*_+d>o#1Oe&j7C4S>%R$jp0-uKX zASf4z4+4kyV2v=HYbP9Hf=(tT*uumFLB;4*2%oX&x*CL;Vg=!)CS(sO`L3nHOiOzu zZXgbbh%4m`LQ@xb4TDYwp{a|Ih7^KJc}m}6UTapqnZd8kRKKZH5pGr@yi#EMmX9+w zVd(E(dywg@d{%rFtrMTe7(_g?0!}mq3mvhrl*doN=F)C_Eu?cUe)hAKg3IL+XE0cC z{1SVD*%D{Z29UwW83UeE!+Q@Nbu#lxVvsWZ3jpkDzeacs;k@`y^rI4IDTtya1Lh24 z6^lIbdx&zu!5>LYN$K(lW5pE^_6>TL3Kq|zDXjV-!3ASVkyf)7N^SjWvbM6U&>kj) z%!+4{$P9ZlS}3!PPK6?+NTJ{%bE1bZtg4)NkT9YKr0I=SUL6;oteMer&@<7}QR^tq zyups7Mc&#|=zmGazAmBcgeU$C^%&9MZ8@HHuuf3LWfv__e3PpCR{(hU4+v+?WunhN zO&VH@A}jfSWIdn||1Uj49gmtbt$*^OjI5Z-Kg)_<;l~F~e0O26=?U z3~9yMyl78J6E-U$`pQu*o5oov6XAYAstv0dm^N|m3H6PpO?Zy!F;Nbce-hI4_wPFa z>t;j!{UV6#hzkPJQ)1TnL#c<4?%z)?97Kw=Q}|yKwub-&M&P=d0AocE7lL`)K5+Qp z9yZjln4LOCPlE=GoL`<`iS~i+hfx)2Jkb%32t{-F;w8WqGCEw6#vKi`5Codo1F%yu zy!V7Cl8<)g&mfc~#~e90ICyyI`1O zxyOe@(M~x+QZ=G5Mm9u5W;TuFkVOZ9LIOnuiV2huC?!xvfM`;Ywhdw>0p{p|>_>{j z1ZZ**X|Re;0_;nvC+rad4FuSTXdCPL9`EwG@Qhh(s>4k2lpI(%1Kg-`%Z~OUWYQ7 znjrjVpp@g{(PO8?6%=fU2E+IW+$qDX+e!-8M#GPKo)kgf$L9e55BpICaTNutPaHpZ z*yG&i5m)0@Y$vdW02V;WDQd{rzq!4yzjw=~p7zfE&1?F%weJ-Dl!Q&89fUm%5K_WX zyqJZ@LTMnnHes-%UGGsgzs;iYLdA82r4J212@cpnbeBgwOAn}|J7!lTq}z*@3=a`A zl9GhfP?q$ZgqL}VRrlwVg$}N&`(wj2q(OA-a)X+(+QQ z5cop^mkF$VmvxL2G=XmG4+>)Pi8{eh! zD&OU*8AMLYecbXe7*DoyyUy%7_t=@oCVTvrCNF2ak!Jo%-l!@pD0$mY>e|nAcx_eJ z&D9q@qnmL|W-yPpkN=iGvvf2gXe^j4@fpje%syl7sQz|ZW+2UeEzLf$%%4^=s=A@d z@S01e_WM*zMwPGzU{Z|gB?E#m=JMR|v%}szr(ah`oKwa5hBsROwDpa)KWz&NSrah0 zr)W})ro*<3pbr}J0>)yWv3RoM9b?6qihS!by9Z;spQdMzwFUKg0eu;yHzqv+eYHv$;xFlxw~W;qqFfaXnxAt>~=vY2|tmQgq0X42j`BLr|bH@()dE2al=gs$&u<#cq3>rU(%=%*1ShgQ! zDKdB!MVjI7gja?0Baz;;yve@F4Wn6JzHV0G;5C${K1|sCwY-{dzHcq#Gs8Ik$TW^> zv5W9>2B^v!EN~|rEGi4y^1|t>_-0W1VI_W1BVeh7!33fsoJN?2Gg;rKRQEd-7GCv1 zy@ub+-?a;Ng_pBpGh#4~W+3@?z7GPQkMjHmKA5JJvFtn|Nc+ozd-^vHI`mj>M3W{XxktP{y@47F=hG8}V9c4Rh8!Pwoq*|0yFyVQr z@`f#U;xjMl!)Z$?vkY@sNyu$$X*h)tYN;)p%1|`bdY`4E-ZO<2EEF_kB;RU3Z^c`) zWKWpRcHh7jY3)~@j%HuYtAfZ>iQn5gLpTM$$Xk;UP9;pr8W`oA6;@CfNLK01dech% zeAzvPmfylte0i9m_w$?hO?-GbT7KDN&Zy1H*WOd4^GhiqoMQmHpR4Dy!zFSpD<@r) zs_|z=>$vA^<&C1U$>ZOs3a6~$c_ZsgLT+0M?z0ferYWqTu~g~j3hRtDukw8M`E(kaEAA=kq(m#j zgoOuViHiJu@jZoIiY^HgcE44@+b1gTbNIpVlU{>YQ{m?;?zv6@-S@ z$3_GtyoXOk`YMos(D={B>F4Y2;VaFi_kr0CSonDC`8q#ee-F8`=tn63?D*^-xsZK< zjmpk@iUYh<+b-0W@bC`g7#T5*7uvk7Y--(jPf^9MX4T!q&oca%;r&gV+6W&mybY#c fpV>@ny6t0TtM{_g(AUTX8`XWQltDQ1Sf%`bd_z`{ literal 10907 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 JAKJlj`yEjXj6eVY delta 1092 zcmY+CO-~a+7{_P2+kNf!1yEip*Z?9W^#F!M3aGr;L{QXlw4^E91y{>%GrJJ$g?QsA znQviC3?}N$=ogSg6HO*D9F?1i%gKxLOeNM$_CK?~eP*8j^UVCnopdFCS(btLv;O^! zdtKg7_QXD+{n3Hl99l)I(sF!BA>*A1Zxo@hM)?k zY8y2L)i~ALXhKkfQ?rd)f+je%INd$nI4N+F<5XL4O3*Z?nKqigP%NRX4Uij zuo}9)7vy71pRWeaZNKK!S-j*r^;%wL@+~(AnRcfELDf2w7hOAK(wx_{=_|aBReC1& zeE7y zI#R^g)bnYkmRzsqdYi`<%_w<`nKdV{i7PVGYsxK^WWcsb-%EXG2ce@wAaO2Cu5nxB zB7LFtq_RLkEdB;Xx~3MbFLIIeoN{`A&(b4x4R_O7Z2^zar`m!s27f`i=|`=A$LMdZ z7kk{~I4$YZla?@cxl{CpP{h$vFJ+V%k zcRvWp8d%-fU~%4fCupyzRY#q-n@qv}MUZJx(Pfx|K;mGEy2cc*XrfgSa8c2rF*`a6 zk>HFJK(26M=0A_Egezp69%!A$RR{qYrT5IN470x*jnlWLWnO~+txCw zMD;|LzBZPrpGXf)aKhwu->shr(!>F>_`76Ex~e-3HmOP-G$joKEsSkWn(+s zCQhY7;9fJW(X3Uy(6xySD@1pcu-V`jgeX8%5Di2JF+s%9i<1;B69o#tA}d^9YXe?x=c(S;*a_>2mXBH^LEW`y_obBu&XkCVN>EJ3xs=?xIwC*e$?1 zn=vfAw%%6z7Eag0mYzqtokjYhUNCaDQ8BY-#n$sK>U0naSs4J}?n?;3BQOF?*DMTy zi*NA|JO&4G#+AU!ki++3SP{kuheixsn#3_z1h2#3J}1eK01aruoV3pXVC*2#*zs1~ zGS!8|!KIN3|Lp6K!vZpl`4lkf@oPn5ooER8~DJo-L2hi5_vnF-IMBx_g@vr+9xcf+RPu zK;VxyN)UJg_cFWWJzyruqcB+!OF<=YSE2(tXW8g#oku#}=Kauvzw<|pI=@&c=w)lK zb#=+s7pvu*ty_o=)P5^zpPm8KMGv9`>f&jlxn0&%sGn@D+t>a8 delta 896 zcma))%WD%+6vpq(Oy)hArb(LgVUm`nW27Zu>7qq~LTELO&!HeR7{+8;r{u+*v367H zwyP4Rf{QL(32p=h{R3Q!P+W8vD^|fpH_}jbtM|4IR#%?InRCy4zw^x;?o(w;2^`9@ z0B{snUz^ixD-eTsTB>8$xIMG97%BXA^?sIqs|l9ea!h8!a1K|L^-WnJ=TTT4tw@!0swk~xJ5aQ*My8u z6DR480#AR5-=z|gCToF7>Qw(703vpu^9_Tv;=J;eAmc=+apyLrJ8!68p)`#YJRfzD z6eKC^e4*9xH1$tE?e_%B){$*JFztIDqc@vIy@Ggxh$Kl)-hY1hW^Sr@rPfU58fB|^ zsj{rs%SO&vE?(8mdh!NpEE#3n+4N_HGngTqweRfuQ)^oxw?FPihum1njVIkm?{3f7 z`<_f&X8Qu)0V^x*E+9wt#K1;&J^OfMM;zQ12VE(+QCKhhVQc@vp4$;q+hXc(cAgmT zg%{m$E8I&8lu2 z*2z+do~-H_KygwJ5SF`unfp~Ei&T6hSIkP_bU;A}KZ2pJAodBU+d%yQ0!Q`C4`vhw JQ5;`n@EapN)35*l diff --git a/backend/app/remediation/__pycache__/templates.cpython-313.pyc b/backend/app/remediation/__pycache__/templates.cpython-313.pyc index 47d72357e67d1418c45f6db7c337565907a2b370..9f8466b9737a9ff67a6da6c0934205e091bef631 100644 GIT binary patch delta 83 zcmbQi*~7{EnU|M~0SIiDIb}`T$orR3!o%4rCbT%Us5mAoF*C0`#wEWzFDE}SWwIWV jKO@`bI;L($M*hiMEHy$;8N|P^u`#lJ6#^1P>_Afh9vK%m delta 95 zcmeC-oWaTanU|M~0SNMHw`ES+$orR3CB(%l#xu7-!6iSrIL0xhEHN)R)g`qo#y2rD wPa&WvKPxr4WU>;IKO^hrBBpLeM!w1am}`XYGKha+V`F6dDg-2o*nuVj0HqKdQ2+n{ diff --git a/backend/app/reports/__pycache__/evidence_pack.cpython-313.pyc b/backend/app/reports/__pycache__/evidence_pack.cpython-313.pyc index 1e968c0cb70bd52b60ed3144a1ed054ab8fe6652..f78bee933acd15a5a53c590dce3ca0fcfa1e8e8b 100644 GIT binary patch literal 5472 zcmcIoO;8-i74F%c{r_QE{t*%_5QfFDNCL%@ED=yJFq*VP0;b6Qwh2wT&M<{<;y#8r$1!x+k8^Ui zdNjw*F~q~UI5%f6etBmsA&c5r*C;p?ns$Q z?YEpGiVkx?Vg8xoE*0tD>+bh8sF~H4qe8#r}XWP z+6(Lbm%SCaI2W}=q+)*;CKYvZUe0IO!Q^$nl-KnFuji$_Zj+bu%O0@tykX-q!zbRT zH&SJ4u-~33l5NG7pG1gbVYZz!=6&~Gxj+RP>;sScZv4?$#UJ(mFRLWLVE8!QG{xFpmrNlmaijUvHabA*y1YQueStYIE zIb|xA5XtL!8u~?f22W8YO~FJ+DRd-U5RSWWE68lcZ7pgjjK_IZHRodCZb}m4q9!fU ziTu1GCa@^S6+(o#hC%UJkfth9nx<#GFe}J7mgEjEOy&nV7%Y1|;EKy{H7!ze82RVyVo_9D8=@B@sC5a`1gM@sJ zIdL$(J4%Bk0@5|iKU18K(F_~1jXtxbwdu~YpE6*6*mMH6lb(Yq5q<%yabCte(Zp#U zEYaGcwqht#cv?xz3CwG_Ma6$#{t#=k0ydor_5ueih?0ckimdS>2#~RmOlgZ4oJmv!OQ0-qpCpE$vIedVX|LYkR^prsFP4P$ z_xkF^dlz#RHS4`=y$@cy-#tb%_A==PS_Ygq>8_m-*9jerw zR4$D(-2n-Q6682tyooOEDCQsC0_n>|!zV@*MI*GL=w(I!#ppqR*w;Pa!qP{;^rG2A znvL!3yUBHm|TSs z3f0sZD9I4F-`J5mxZ3bR!-uo$(wemGYTj{mWL+JZ&Wqcw-sMZVKuxCM}-nG`XIgky8Rvb@Rw=MXj7S-TQ|8{lP^4J%_BOAlp z!Pez#UqD~ucFm~_Te)lV->=Hrs(09#%_7_@^G%OwK}|c zAzRnA;?H^Nc04%i!8@L&tfwhgcXVYq=d0dm$og7#yd7C@$JWH7{tTst_AIt)|9u9I zc}+{Uy6wI-S5duwe(n7Fg`Z!@RUO-G$X0c3G1;n9JCzr+l^1ihM?Ud=?0c}V;mg#% z_NX;)MOD3f$Wm4Pq#Oi&Q;W(Qb5+M5-h5)Ul-rkwz_RwT`$w|2h8?yo%eFnJdkC5X zHLzP&{mJOZqnRVGKIqBRoy?YX%Uw5oIHke;`Z@&B6_kZ>N*4x{y-`{S2sQCR%r3d%3K-=JH>t`1_;Qjctdx&ww57sn# z`G9X`?-xM8y^WErQHu#Wc!*WbB3o%K1CYi>8F<_Nf7c$f2E!_LW=O}^e zCQ7eC*>*QKH{)?d)jEwjgRj#BT+Fk$uUI`*_XMqZW~nKZ$zp{BykK(SX`&=Cj}PW* zKp&m7R-Siv;@kV_q3d9IxNQ_-Ly1EE$6OybQ7GV$pVN}2J~!JeCoj(I7g98%%JD^t zdk&duW-g<8yf~i|;*jrgLeQY1P{?$+l!IG5SdIz;zhy*7yijO_4onM#s`1H`+5xa4 zfn{L<;Fg*eR6rX^s1qWTO7~I-{81|Tc%XN?QU-$FBH4;?d^D}3}fgSR;KV! zztIsiI<5+UOyCZlpnYjK^)k2_zcmY}#{e$|%rX5}H0>cCxx}ji;8Nj5ci=XzP~Jo4 z>~DLdH1mKv=F<{YYf6+vfKx@Y;>KPB+Ls^$bBk|sXpM7gfImbFfiXi%JzxVG1d@110PHS; z|KnQN-5th#0KXS8;7m0Q2vXH>Qk2CcFM-LDLP9hU5LPuIrH0)-bP z6ptW=NKBq~Izd@qp&}8KG;M047^d$epk|WHSI5X@~q}05C%DhPn%m4nXrHuK{!` zA)F#8G4`?;1J@Y;qM`tfVhsaakdsspeMR6;oq`u&;HsMC(Z4#%^A<-vyW-CWECK(n zzkcPNJYpJrIbX%fAW&6-F9Wsf-k*7YdE=AGk0-YS?K^>?&7&$COX!_E#Egc#9n#`Tv6ae?zTbq1s(nV8>PexvPHT?6&K~vOVua6U+@JZ|!H8%A7w~B#s_05~q5K aL~mb_cyqi!?Al!ave8*jM=h8sSolBaL1T6R delta 1890 zcma(SOK%fLc=qXi_=#gD4GA%XG;zVCAqkKc3ZW?>Nh#p1RhOh%%xc>z>0Rjs6Jte*^HQV9b9wf;GV zZiFiKusKShb95LeOpa+nHVlUOj0}LuC3Yn zEy`VHBN0hSZqjs;p2+QE3=K><$7Q<-pXeB+S{`w5{3wnU%muSO?iHs0WG4VQIlJAD zyn%DU*5uAraIISQsHx z*+Ncc-a@5<=+8zewevvk8ORs~#9)2tfu z^ETOQv==NRYXRq1%|c0p9=~9nQw`lU#JyD9{Xu+v7g;$^T1JKgRR>c(-=HWHk6Y)>o~ zwDT&mEC)r2jKLM71g6}l($@VpS>V*><9#^s*4$L7oSLvSdv07`DwrCafJ<{H3Wk|F z)w+}0C+zKypQ{*)x(b&J9rpvOBoR7<;|1|&|BQ#O1|EnpH!Dw#JmWzmc{e#-Kb5cR z#YXtk+T;T%c6ZC+)qMTqxrX#f?cE31p#5xp-mF(IHzJ?aj(-*0y)t=g=H|?*aA(K8 zkrVa2T4#qF?AhPA@Rx}Om;9!)!S&u}2OI3*4dzzhX5c4bb;rFo->aYcs7|E$7lu6z zZs0yU(qKn!ok-6;iEjJ${YLb7ZHl;TZE#)p*?|T-;BF6H zJ$O4ZaBPUV{q}AQe;tx0K|p){9&oQmhukO8DR*D&@b}f&FVr|$JH!#N69Z2)Y^yb(s0;Hp zNt{ZdgTx^1gMOkphCvOi(eCy5=t}GWwI(Kl>>5aL>^jzfw1kE+LOcN@FR13M3=x(L z3?h`rBj{9+*)&b_t#NwU1daQ~>4+z(>U;$|KvhM!4*<0T{KtZJxnHz(UE-ZfWxOXS zKoq1?o>Xy+MSBmt2sK1DpgA$kyIce42oaFWdBVYxrB@c=J7^NKouL)I9Iq8@@B gkAeI<2tNY-4?)Kx(DfHcJ>xs5w!il!DYhBqUy)p|!2kdN diff --git a/backend/app/sandbox/__pycache__/verify.cpython-313.pyc b/backend/app/sandbox/__pycache__/verify.cpython-313.pyc index eb236de35d8981f413016fa04516de43723de58b..eccb59dcff116877cc8efd0c8a672ce67252d90b 100644 GIT binary patch delta 472 zcmcbo_EC-RGcPX}0}$9QbIN)vFp=+y6tiPqI3raHc^AdA5 z{^4ViaCEkc2`x@7DvrrY%*-o~amg>w%gIknnXJd`&e$@!j9F8QomJ!xpTu=O)k}P; zH+Usxs9xYvSWvRX>$-{SMHAPL%uM`T9n2rt7=%P8A7Q@EC^LBu%U?!?&9hhy7#USJ zpJwysU^50fsrbNT8GcQ%2V&AUq+CC;GRyLHxJ@wa^!>uYAfhxmoL^o3I|Gx1_zeMx z3(^J`1q|-F6-<+j1a>>z;1IYVta_0{^*$RgV{x4tE)U#zdvxZZayp+$;fCmSyot8mWNT~iyR}P!50oDMzb$m+>8QWW&9Z# Kjf#|k;RXOcIF>>H delta 453 zcmV;$0XqKiCf+6v^9>CO000004(GOM+YgZr)EWdtX=8NS13_+i+6Yf@bZKvHVQkt2 zOlfm;vHT7KBuzv*TuE(kAVhCtb6i1Wc42N~Wkh9mTuotVZXi%{Z)#;@bdw?jM*)A6 zY6B@H1qBq`4j9)CCDINhpnI@I&?Y*-6wGSZPG8YZU(pU=hy&OW6qCIJ*8v)nl?43( z9kTr=-+!G;_R1YT^*B5WV zSkO>q(M4s~L~YSTZPP?=h)CWK7n7O~dmsb}JKPc^pjguqE{H?$3IG%rmp73&m^srE zCD#)xs1lPk5W6_AD9}Am(K1ihF;~$sSJyFF(J@)v3lWbziaeMI(+eTd3n|zpDzJ3W zM`O_+WQazPIMECo+!G;_FcC&r(hx?-NYG$n(FkLJLD(4@h&s^(7TgONm{h1H(F-xq z1TpyZO92278S?-F2|L&XI@Jt1 v`|t$7&-6`tiTm$O{{>0hKIi?S8Vlp@)&;UqMy7_NRCk&5eR&Dwwz5NqyATA5l3 zv#UtrNk=LdrH7zGdtomvbE=L#1~~*ilF&iKL;?(?K%1Lf8EuhM-z>Re;Km(-GjHC! zd2imE_rBTlV9<}CP5$XuYjZ&Tz$R|v8w7VwfcOe2Na4~b#St#fXiwThJTUTUJ|z&r z;R$JPN+crCUPVm%QkdYBL?mbKOZ!tYksTVR1F0YhI<%AyrNSf(w5$Xae+;b&YaEHJ zcoM-M*#Qz9l~vO$*s5(5OzR=cg-5Er%Eo!avVZh2ArJM<8m4BLD-_Qca*ixWw3`L8 zuI7!Ro&}!1QXo6P^^r|8n_Jg_jOg1oQFC^7Sy$~%qFXtp&TSDOAPU@h9{R7)b%?;H zoatHQT!E)?#iQ^^-sCe#5yoYNOd%sIG$ZFqvGI0ua;-UPHxbJ4iq{KsMZB|xkUHXU ze2n8tsRIZtGKSL>yy&~2rQ6L@D1Jo-P0l{JkAQ%bVA6Y?8;0`^qYU@#1JKJYV0UM0 zT!8bw*_06c!bwk@?FqiQ3Yvt4@c(j^h|-qioE~;wLW?YNPFrYqsn&S;|7w}!T<zigdeqiza@HOsCRyAtSv-ru)LK|1AWn9S2~V9p$N&$XK@$}p~X&jpAV^!-EqiN%QFbs zx{N0ILA1;zu02d(idPIfudB~3Dr&l|=jDgC zBq@em9d}?>Ai&piAWMb0hqJ#fc0XvIHomMHd0mU!g*X6>QR)CwUg|M4id_(**wR1O ziDBifFM2oa*Y#qX z;>0Q5xI|sM6F2?=5Sic!t90tO*nzWoc4BZ%b0`RbsGBQ{ClGKjfg6^4UGpTyD3;k| zlyzg!QS8EaB0|{120B>uEW&V;NDikFSn6GWt{H^-x8N4^tPSq~m58ot4oMisQE$GW zY8I7dwsX33F_d4?ZNgBKit5G&oR#vHO$fu*L>t8{53@|GkEmN&NGV9Lb&Yb6DvcDS z0*eym*^&Tu*U1go+6aT!2?OPsR)J*$^)EM`w5*{uBHqo^G7VWs^=$L$f<%BL%EQw?)#X}YjwI_@pOW13riWaJC>(la&p9Lz6)Z%fS^w3gg7 znb_E&0rqmb43Y**?7}S$%48S)(<8~^?$ql}Bt^dDD@qN&w|oC(uP0 zrt-KNzxhfQ+uF}~+!;DUI2bx>N0IiDbS5GkuK8kh9NrJ@g^qE54fmHHA5K>pl?3)N zj@5AN;Qiyiv0C5Q;gf3LSha8Rg|Eu^kIn?%+kO)6*e`xjJh*xs8?D7g4))OFKGInd>L9$mw|#)i*N(ztt+`nC9EGpIJkt3){9Am`cNBT2 z(8?Q$P%bf?IdUT``t@hq}C3!FVtN7CE5b7DJj!eEt z)w-XQd zkj{9ZezMq0|84iXv%~1S;lS)I{=1tT^a<`^LL_%#jR>rpbSYL73}=f$_8GU>{CxWi z>d2qXE^o50v22zAPM}6L0wUV0dE=R*hKlyi1|T3|A4gPN2dvIFtDF@hjcnRR-kNCQ zt)W|u;`cY$`ZfTPgAZ*D#?R` i=<*LpK1IE!sORtK>M45f6va=`jemzS7|U delta 1366 zcmZ`&OKcle6usm5kAHFO$=69H!D(t#l0s4;M`;k$LFH4_<}p#hk<7^RNL?jk^JW|( zB;;%=Q5K+TKnfsLUIvK;8(<3(Y*?kc(I^p%Zpto^0U=hr=hktN=#}QYJ9p0gzB|7U z{F<6+$Kx7MdEvwFt4k8VefH9o$U}u|uicq>Bd#K41a1l>wh_r|ckaIQx=b2Qxw2(f zxUK^|`{=_1aP1cL{v752ao0G9E1hz#_n`>l3%nCC0SV7U0UX}s{WWg7nFlZ<0Z_ak&T%=KJqP1ZqJ2G-Ri&%- z&M7eYRaymrFGNz!WP!WLb#vLY2Ln*wg-6vp!W=lFDWu`@ceHNFlvb0yNL>x3kb2?K z>>T+CoPLx(2&`%lK6Bq-%M2|Jr&l>w551pkQ?728mQ(R5? z5BO{o4ViKR(6Fg=P))$RM2A0uMwxv+Qc8C#Go#(A=yhZTD6yy=xqjQ!&{%;-flhpj@{7+yY74>l(4hGJ_w|-iSsgHWs(5~ zgA9fk3{wb|&059ssx>E6wy$3bcG<%_ zUZ}3@RB*i$fZxDgC|UJ7Ehgk$j}W$#`B?OLB)LwsP6r{+_=1hy zip%C=JSO4Q;^O)%TXmyYtGMeg+S`^>!9~2ie#NRf##K_gNk`*Hl%>Io)RX=0?lxWM zT?nA$6%pFhlu2I;4Q;mKflA{dwrvSWKUzIbh4+|L|jXHTM%o;U_6lcW$& z9w(r$|0C7s)K8j$68%)Wt$p@%Dz_Up`2l5A?A>p9nJ3ExWaL z(0}f5XmqdmmD-waEgTG;J=Diq3qM}Er(X_?r@t-U(+ej_C8PZXiW)tMLrOm$hgfp= z)xZCkftcO~PV5hosgKoTo@)QNbc+Y!r}VkvGW@(8TZ!|(#5jt1fjsAXnvsczTDkmg z!)p*+E)%*J^oIO>?b)}rhF9HmXYmf*9+jEdWWI(rMIcKQLS<>IX4AD;CNI-hIxcXt g6lh|OJA$eE5IusSBN+S*&K!a9r!>ta$qFU>3$sKJzW@LL diff --git a/backend/app/scanners/__pycache__/osv.cpython-313.pyc b/backend/app/scanners/__pycache__/osv.cpython-313.pyc index 1ae5fde7da1e79953dd8ed3a254ad7d2afa7ba1f..d7c3623c16bc2d4d99ca62aa1599f512543ce642 100644 GIT binary patch literal 5650 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 diff --git a/backend/app/scanners/__pycache__/semgrep.cpython-313.pyc b/backend/app/scanners/__pycache__/semgrep.cpython-313.pyc index e6e7f33a4626f7126656a59fd92300d63fc35878..fab1524c8f8d77a81cf6385816d85b6bb44b6b60 100644 GIT binary patch literal 3687 zcmb7HU2GHC6~5ycd&a+G$4;Dtgg7At3^)cB3dt5!Hjp2OB-F#~QYDqw*pp;}GfwY} zH^EiA*{3?Y4}hu)Q&l1lRpq{{c;vZHt9?r3Rc&1nHR`TZ@fHNqR+TFC+_5J?h&=RK zx^wQi=bZc9d(J)Q`nA{VLeQ@K<)fwlx)J)G4y?vD2S#N2?urnZI>LAd_urN!ik;cn4fed+}Lf>!lWnR#a@$kPWloe79%K* zB-hUw?3X-}S8{ivC2onqfdyOC_mTpMGQ^$Mw7iiv)V!uo!InT^{nG7Vk?O|>KBMiF#~G%Im$OV7fJ zjdmwOT`|OJoXp%EMo4Co4r}htb5gx4%*iG@3wR*Z+AQmuJ&rVoY=1BQa2#3q-?b12pN%o&e*&-ZGXGxf zcB#YC)`UZ3I`?rn50N-xNxX~TEPC&%U6#D&I>chPCGme-bRS~z=;0PMkL-Cb&YpwV z(7cDpL=Lkn1(F>HezvHKO+xd@KB;&34#XGSCaF&oXF|X^w$JGYjvF|~KLiabBm013 zlLDG34akBINhf5d*}Ht`1Bd261mDTS@EzitsB~&SA_B8VEZlNnw#%Zec`IJit$;4D zKk~tS>9J(xfOMKhw}oQH_4b^_)mmgh7Nt(vd6tpR$Su&v201M_+q-WkC^6FDkWCB8 zj^r_md+PPeF3fqI`ndtdx!t2WN0#;UvLM)caYxC+t(GiO+>3OZl z$6~)KsEVQ9f!hsfpUtdhiPKP*m3+ZS>9=VmSV?0=Gjt*-YfvCk`Fq5n8(EkqsAw-3 z^Rdg2`2|JG4#Gl86)6=>^xQXe}nPRKd02rB1 zLv_eTjC=%Yin$C3tEC`dvlE+|C4yCVh@dN9$+05SPpkmYbF9A*DS_(5O{Fx9z6rq zT)`v)ie(v7LHJd0!(VBg1pDu3&v` zDXnU&bJrmn%_EtE*H~upmYT~Ob9dA5V$!m6uHJ_>iqg+rqi^YkLkPh}(Q;T@WAmHheXqYg_2ry#4I@7Nv%OT@yODh0gN1rx%}G z+-$F$uO1nE9^RtNYrDKq>iXjI@_d!=|G^Wi2m4-hoUV*kJ3gp)^wis8^{(#n;*-gW zy%O5$8hqt;czt`U;CJmI!RguyvG$f1V(>xn%VN2=JpSZ#tus~$RmH&)wU) z$Kglea=04ktp!eOCTspP+x|0^kE{L*CA%eB`0BSc@z`cd&3AI!ce2t}^_?xTyZ+!~ z{t;j1tNuvMKd|``$oqm1)D6|d;2VIo+q<7SpEx&9Roi2=_Dj#&YT>Kf;j7i~&q|(M zces41>h7zDyEn$`-j4Dws@{QmxW^o9btkIcL_K_LV|+K%^6BCbru?F1dFb(er{jvirc1z4f-E^{$>59le{zH|%!D&HBLL^XPWR z^?FDDtANw%`;m1!U9Tn>%pp7{406^Pc?h_ScS#lV-&Jr=0{==3rAx8r>Y z9(~Ju5^e0atqk;0W;*J?qcDd#+KpKhaWra7(M$ib%r^h3{4&}?T&dK2f&TMIr7*ls z5WWf>En&uDPQ7cIAr9mI3jDLd^Z`H|%TT9sdLuv4yh8j1L(S=f%|fF>+A!0WtHD;* zluRSz1saUEuDh9}@MXFzy^McFuc1fyHgrVzDJ)dTDOd5QFr)=hr$qwx&oDcv?|bCl iLG3#z{4X@HgU;=s$PPOD&z7lm@khrL!-zNq!v6zz7E%`g delta 1376 zcmZuxO-vg{6rT02ch|eN*MEy0+Yp;T5|a`_X%rb~)ztjCjbrLz1;R+KYcJFYuhG~J zQF?$JDv|aO=(Z}ATFHm1a(gLApLy?n zGf(r%zv;YjYO!zt%e7a(gt(56E4a_Gmf#G#Q+U&!g>Km_;>BL3Wg$A$ z5z4E^jH1PT;bYHzp8xTg&V5(fKEVrp7j?LQdprH@tpDM`Cj|8;%D2u-RKnh3bQv!l zV5dY|0ql~PGi0q`h=!cR!>c2BLmG3G_&5Q1iI%vevz=q^v1EdQFe%aa8iThDush7O zdkKI^A{GFE$LGmPR1XM(B&U8%2Z?LyxAdrLW(8<$|ZH1nI6>u5^>@uY*EfH zA$fIJBZfmpjg2ItGttQX2O2dMot)C>*!WmfifQD7_$<;ng-GO6KV$vSixrKQm8F~_ zhZv-HiSU%P(xMK1<<>*kl|m+6$`uri&C8{9HeE`GIL!jbW0Q&LR3!dzB07modN`wo zO#XFpNiJ!|^6ILLG&+rcN*3u;6f$cp?z51}=8#6>@zw~WQF=O!#H2J5$wj0$g?u{r zb?CyNk@@1%8P}p2tAR_R#m9yhqWRUpSRqqfxSxHLRx);ZOn$TwOXrlpG%761nUeY* zH!SP-qHiz$h(~n;GjS zO`)qP`t;8cYI?5h-29raj8|s%J$)xUZRd^wZR0VR_+y(I=RaR>3XbP%&(^lq>cZ8A z&|7)EVeP9~`>NsH59`+OCfO7{U(IgM?#$N3-iCOq>S(y`)LeIVy>-`U!!_}OZa8B# zXRPjwZ?Z=$|D1cqeQ~d8$Ax#imBGqPWuh9aj#uyP3qyKS!O(vHd_6e7J-7eCV$0{Pul_FTWAbzNgd6O+%@fzjy)Nmk0H$-&h8- zT)C86Dc+VJ%bC_Zyr%o~gZ}}XVgzUygT{{L3)%9D{1zI+s{U{kgBWmaf;a@-F9ClD aJcq#j8@PT50!Oxv4a*;N4WJjAw3vf=hmKag1Y1Sz=yts!M8FjBjFQ Sou~x4mJR>trcGY diff --git a/backend/app/utils/__pycache__/fs.cpython-313.pyc b/backend/app/utils/__pycache__/fs.cpython-313.pyc index 66837b08883ac417bf779fa0b87e62090a70f790..0fc3e706c9705b0b70820553fa308d3641216d30 100644 GIT binary patch literal 5164 zcmcgwX>b(B74A89XZDtMbt9zJ0n#D~0pjusLy$2N$P!q~7&$Alwb~s?gLY?@nOP2z z#7a^XORh>ujzbVfg~f3h5$90;W=2q6e81ypi5;O&aU)mA3_~o%fH{MF$VYUe{e(=oF;8}r zGO5>;OzAZ((|T=?4Nz0v#{G=UfGq2yE6`C|W`Q&4oXKbM8FkKFg_eJOW*?*XT82rF z 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 delta 906 zcmbVK&rcIU6rP!#{Ykf=AgLHaQ4(4UL4rL<)WifN{(xxLDiO@4wu>xjcQ>-gsvL`@w7Bzs!a}|A};80iS(S!<0j=vb8QDnsl{4kd={Z8Txti2WJxk?DVRcdCi8c9 z>E~g}qu`rt*&M!s?TGtnXFDARau^DEa#1dH3eJ=**;3AtG%8Z)=?&?P*C;iWxvrMJ z&>c7EvHs(&@1oLbZ|;Dby$ux}$)jzDsvb~v#n%l3#tp^(-@5zX)}$!MJ92(Ro#B{$ z5o2Uxjswr#7~r)t18!kw}Vo&$W^}{*%g;@yu!8e zqFb#n$Kz7u2M}rjr*7obLUD8H=3)s^CQ9 zMj>#8IZ>T?oX+^3lU3NMR)!56pqxNM5V8h0Y Date: Sun, 5 Jul 2026 14:14:59 +0530 Subject: [PATCH 5/7] fix ref validation logic, blocking call, status code, input sanitization --- backend/app/main.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 21e25cb..d22d324 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -78,12 +78,8 @@ def validate_git_ref(repo_url: str, ref: str) -> bool: - try: - # Use ls-remote to check if ref exists in the remote repo - subprocess.check_output(["git", "ls-remote", repo_url, ref]) - return True - except subprocess.CalledProcessError: - return False + output = subprocess.check_output(["git", "ls-remote", repo_url, ref]) + return bool(output.strip()) _MAX_UPLOAD_MB_RAW = os.environ.get("MAX_UPLOAD_MB") RANKER = load_ranker() From 64bb6d3103b9e4f7d579f11c08df23599345957f Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Sun, 5 Jul 2026 14:20:40 +0530 Subject: [PATCH 6/7] fix: validate git ref for /scan-url and org-scan (closes empty-ref bypass, blocking call, tag/SHA URL bug) --- backend/app/main.py | 62 +++++++++++++++++++++++++------ backend/tests/test_scan_url.py | 68 ++++++++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 15 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index d22d324..8cb3dbb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -77,9 +77,22 @@ from .utils.fs import ensure_dir, safe_rmtree, unzip_to_dir +REF_PATTERN = re.compile(r"^[a-zA-Z0-9_.\-/]+$") + + def validate_git_ref(repo_url: str, ref: str) -> bool: - output = subprocess.check_output(["git", "ls-remote", repo_url, ref]) - return bool(output.strip()) + try: + # Use ls-remote to check if ref exists in the remote repo. + # git ls-remote exits 0 with empty stdout when the ref doesn't + # exist, so we must check the output itself, not just the + # return code (which only signals errors like an unreachable + # or non-existent repo). + output = subprocess.check_output( + ["git", "ls-remote", "--", repo_url, ref] + ) + return bool(output.strip()) + except subprocess.CalledProcessError: + return False _MAX_UPLOAD_MB_RAW = os.environ.get("MAX_UPLOAD_MB") RANKER = load_ranker() @@ -330,17 +343,27 @@ def finding_key(f: Finding): ) -def github_zip_url(repo_url: str, ref: str = "main") -> str: - repo_url = repo_url.strip() - m = re.match( - r"^https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$", repo_url, re.IGNORECASE - ) +GITHUB_REPO_URL_RE = re.compile( + r"^https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$", re.IGNORECASE +) + + +def parse_github_repo_url(repo_url: str) -> tuple[str, str]: + m = GITHUB_REPO_URL_RE.match(repo_url.strip()) if not m: raise HTTPException( status_code=400, detail="Only GitHub repo URLs are supported right now." ) - owner, repo = m.group(1), m.group(2) - return f"https://github.com/{owner}/{repo}/archive/refs/heads/{ref}.zip" + return m.group(1), m.group(2) + + +def github_zip_url(repo_url: str, ref: str = "main") -> str: + owner, repo = parse_github_repo_url(repo_url) + # GitHub's generic archive endpoint resolves branches, tags, and + # commit SHAs alike, so we don't need to know the ref type up + # front (and don't have to guess wrong for tags/SHAs by assuming + # refs/heads/). + return f"https://github.com/{owner}/{repo}/archive/{ref}.zip" ALLOWED_REDIRECT_HOSTS = { @@ -720,10 +743,18 @@ async def scan_url( is returned immediately for tracking scan progress and retrieving results. """ - if not validate_git_ref(repo_url, ref): + parse_github_repo_url(repo_url) # raises 400 early on a non-GitHub URL + + if not REF_PATTERN.match(ref): raise HTTPException( - status_code=400, - detail=f"Invalid git ref: {ref}", + status_code=400, detail="Ref contains invalid characters." + ) + + is_valid_ref = await run_in_threadpool(validate_git_ref, repo_url, ref) + if not is_valid_ref: + raise HTTPException( + status_code=404, + detail=f"Ref '{ref}' not found in repository.", ) job_id = next(tempfile._get_candidate_names()) @@ -1356,6 +1387,13 @@ async def _run_repo_scan_task( repo_dir = job_dir / "repo" ensure_dir(repo_dir) + if not REF_PATTERN.match(ref): + raise ValueError(f"Ref '{ref}' contains invalid characters.") + + is_valid_ref = await run_in_threadpool(validate_git_ref, repo_url, ref) + if not is_valid_ref: + raise ValueError(f"Ref '{ref}' not found in repository '{repo_url}'.") + zip_url = github_zip_url(repo_url, ref=ref) await download_to_path(zip_url, archive_path, cancel_event=cancel_event) if cancel_event and cancel_event.is_set(): diff --git a/backend/tests/test_scan_url.py b/backend/tests/test_scan_url.py index ff9c3c6..a191887 100644 --- a/backend/tests/test_scan_url.py +++ b/backend/tests/test_scan_url.py @@ -37,6 +37,9 @@ async def aiter_bytes(self, chunk_size): def test_scan_url_invalid_format(): + # No need to mock validate_git_ref here: repo_url format is + # validated before the ref is ever checked against the remote, so + # this never reaches validate_git_ref / the network. res = client.post( "/scan-url", data={"repo_url": "not-a-url", "project_name": "test_project"} ) @@ -44,8 +47,43 @@ def test_scan_url_invalid_format(): assert "Only GitHub repo URLs are supported right now." in res.json()["detail"] +def test_scan_url_invalid_ref_characters(): + res = client.post( + "/scan-url", + data={ + "repo_url": "https://github.com/owner/repo", + "ref": "main; rm -rf /", + "project_name": "test_project", + }, + ) + assert res.status_code == 400 + assert "invalid characters" in res.json()["detail"].lower() + + +@patch("app.main.validate_git_ref") +def test_scan_url_ref_not_found(mock_validate_git_ref): + mock_validate_git_ref.return_value = False + + res = client.post( + "/scan-url", + data={ + "repo_url": "https://github.com/owner/repo", + "ref": "does-not-exist", + "project_name": "test_project", + }, + ) + assert res.status_code == 404 + assert "not found" in res.json()["detail"].lower() + mock_validate_git_ref.assert_called_once_with( + "https://github.com/owner/repo", "does-not-exist" + ) + + +@patch("app.main.validate_git_ref") @patch("app.main.httpx.AsyncClient") -def test_scan_url_not_found(mock_async_client): +def test_scan_url_not_found(mock_async_client, mock_validate_git_ref): + mock_validate_git_ref.return_value = True + mock_client = MagicMock() mock_async_client.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_async_client.return_value.__aexit__ = AsyncMock(return_value=None) @@ -64,8 +102,11 @@ def test_scan_url_not_found(mock_async_client): assert "Failed to download repo ZIP" in res.json()["detail"] +@patch("app.main.validate_git_ref") @patch("app.main.httpx.AsyncClient") -def test_scan_url_timeout(mock_async_client): +def test_scan_url_timeout(mock_async_client, mock_validate_git_ref): + mock_validate_git_ref.return_value = True + mock_client = MagicMock() mock_async_client.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_async_client.return_value.__aexit__ = AsyncMock(return_value=None) @@ -83,14 +124,22 @@ def test_scan_url_timeout(mock_async_client): assert "Network error downloading repo" in res.json()["detail"] +@patch("app.main.validate_git_ref") @patch("app.main.httpx.AsyncClient") @patch("app.main.download_to_path", new_callable=AsyncMock) @patch("app.main.unzip_to_dir") @patch("app.main._scan_repo_dir") @patch("app.main.get_db") def test_scan_url_success( - mock_get_db, mock_scan, mock_unzip, mock_download, mock_async_client + mock_get_db, + mock_scan, + mock_unzip, + mock_download, + mock_async_client, + mock_validate_git_ref, ): + mock_validate_git_ref.return_value = True + mock_client = MagicMock() mock_async_client.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_async_client.return_value.__aexit__ = AsyncMock(return_value=None) @@ -119,3 +168,16 @@ def test_scan_url_success( assert data["project_name"] == "test_project" assert data["status"] == "running" assert "job_id" in data + + +def test_github_zip_url_supports_tags_and_shas(): + from app.main import github_zip_url + + assert ( + github_zip_url("https://github.com/owner/repo", ref="v1.0.0") + == "https://github.com/owner/repo/archive/v1.0.0.zip" + ) + assert ( + github_zip_url("https://github.com/owner/repo", ref="a1b2c3d4") + == "https://github.com/owner/repo/archive/a1b2c3d4.zip" + ) \ No newline at end of file From 1d55a441b32d37b1d2fe1df449d2144d5999ac49 Mon Sep 17 00:00:00 2001 From: Kirti <162712321+Kirti391@users.noreply.github.com> Date: Sun, 5 Jul 2026 14:46:09 +0530 Subject: [PATCH 7/7] trigger CI/mergeability recheck