From c85d3e918841c9b468b04a2d921b582acc9f6f13 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Fri, 17 Oct 2025 13:42:48 +0200 Subject: [PATCH 01/57] feat(docs): add `M365` `certificate` and `azure cli` authentication methods (#8939) --- docs/images/providers/certificate-form.png | Bin 0 -> 421533 bytes .../providers/m365-auth-selection-form.png | Bin 0 -> 291797 bytes docs/images/providers/secret-form.png | Bin 0 -> 354389 bytes .../providers/microsoft365/authentication.mdx | 243 ++++++++++++++---- .../microsoft365/getting-started-m365.mdx | 42 ++- docs/user-guide/tutorials/prowler-app.mdx | 25 +- 6 files changed, 244 insertions(+), 66 deletions(-) create mode 100644 docs/images/providers/certificate-form.png create mode 100644 docs/images/providers/m365-auth-selection-form.png create mode 100644 docs/images/providers/secret-form.png diff --git a/docs/images/providers/certificate-form.png b/docs/images/providers/certificate-form.png new file mode 100644 index 0000000000000000000000000000000000000000..b19c079d235ef8fdf5199b679c5711e80929b014 GIT binary patch literal 421533 zcmeFZcT`ht^ERr2qA1uX0wSOS5_<1NKsuo$p%>}B2SP_dMX5^fO&}yR>C#0+Iw+xq z5Q-EDReFcBdEVzO>zwud^!?6SXPrMj)(UBR-#h!hXXc){=9+n<305Q{r6oOg?i`u2 zlDzi0b63FU&RzU``4aF+U-4WT@O0ioTT%90N#9)p@Fu}ZPuW^s{Tv8*efb>mdD?Rq z&kg||Qs?RZ^;+TlqjN-meSYEGxlp@v#D5$L*$f|foF(?yOou*hph`dZmeS(_~44GlD@~eb5zV{ zkMqjf4}Sr_Z`2N|2iH?q6SHt};xe;zF}LFKc5*!%=bVJM81T}`3U0>e?d0g}A?7W4 z|F0v&fY)aqbKhtD>kznutP`%S4!o1R zZwrUJig9xz5C|>=KbMQU4L7f-s3{BsKJ8vsTeR(@4V9tPRNbw2?3rPHR!vEu|e_!%HPSyMOseJq*eE)gse|+_SKNaR- zsa_zo;xRVPFemj)cgE0-ZKq4MAyC^Y9g;faK7^R zaUL;Y)ta!ndI@-}g6(b)+u7yHJ*2#GbMO3t_|GE#zU7|fp3gT%=qlwpZ&Y>ium)*G*UT@RKq9d&J*8|`J;aiWL(39FZ|gR z&VI=Y(euF&v(&7%2B?eAa(Yb_blor~6esiH+N&;Tn@}kJbG@Si^rDA|rFbkymSqnE1g+XtMr9=jP5zFVmhLF=FAcqhhh4z(+qw?{FZEe4b= zM&;F(9kXFVG;`pQ2$zP(&%OW}2}cRYUM+<=$se0FCdE&MEhwfCUnO-x&diFr&RAr* z56&4|Ytx)28Puttc-p`Tc~zUo48{Wp8GY zcnSSmG{X7Kmfb1X=@L&*)RXR!t}wyWyE_+uz5-FIDEFf0(si=M0)JFiI!!7%j~}s# zOkNmy_2(kRMLZ!Y63?uvkj!iK0)yJ0=g{NA{N!+RTYUR{Jh!QIu+(^H+I)L3ATkw^ z073SNF26tj$D0OmaLp(2i%7VwearazEpZ*S`uYaf^{|M$!knYvP8&qoJ;w#MBe5zc z5CM}{y>z^=ej8<6N;b_VPAT+<`w}2&Cl_4k(*`h`oThxe*#m`zC*o z9>QJNnZssl#IxJj!;COaJHI@i^TZC%1kFQicT@}_<$jNlctI-mFP%PA__vgF)^QlKS3Z~+s zH7Kz49#s$3?)3X|Q*)WTJLnbDuW23VN<%$h4yYR(Hf-|v*)+=z)iP=DwsW$$_+JP{ zrc72a#8|v0E}?e`i+}Py!FYCgU)9UW%<2K4KztqpuJyQd0jrX0biAD|n)`8rFokznp?lX<+dhNb|crQIM)za+$df3hf;K)s+bRy*SnVka<^GnE zVJ97VJ}V`5epa#??FR1??E$MUi~p^`om@eRJ4NN@1bAUfqu=$s1{VupT23z)8S}~g zXpgpVD$s6xb63p&a~(YeJ25g?HlFuSO}K3RGu#w3_G?w1?yitdC?{@L>6B>(n3tmm z-;jQWY+4VStIVd}uJZ+ay|Cn^S#%?floa+={*Rn-!04@9a1Mc3CD2==LT!>hl{i8F(%~ zXa}cNJ6CK(f3oSa`%!Q*{A#8P<)3$HcqL2kN>mH#_09V#KfYXfXcr?N!=ZnF{mor{ zF5_AzYHs6rzg-66i$oUpZ~P$k_xFFOKk#Mo^hJzO{HM|Z2V2FftO^l# zZVj=99c?Fe9FzDDJKlV+5DDVVX?95o+}|#12t4lc+BGaze0{^@hyPwS&cd|OmlN=+ zwEE{XY64o_Lm$4mtz29FWNr|(S$_KIW~pCW1dW`3&aeH;-D|w7^uIApr)PH@+Jt)u zxE2-nbWzrj{r$dxzhvQ|Hn@;Vy+UYQ3oP2iVTC6Ti=U!M3!`UG7%b6xyf|EP-|fd2 zFW-8NjHCQugpK8B0m^;)Gcz*4R-RGdYOYGMEUj<>S3HO!@e%0Dv}3K`TK4i-qp3nf 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 Y8Mcldb-X^J7+s?P76{?qjfzB;T)6`$U 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

aLIC) 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ℑ)?>R8Xj-#8xWTG9-BCfeKAjCn1Y^~4_8)ZNxsa|nnJZ^s zRLyl{l3X?q1h3Js zqnBOG23Qvn!x+nX3ZDZn8&aVMzwwcO;ZhfsK&imKXszw-O$dzQ3HfYra&>shii{1( zB?#kWcbvzA>Toy#SPoGuNuFBY*WoMCa_vuPjZUt$$9?`Rl=Gd?1Ep(Ui!ZmbXw>x0PQVqdgX+o$Rd19NL*6x|TkBKX;lPh(%CV|kK9DU2sFsDag@neOxLCeG}?)JH<|(+UT%{))7f0s*-fH@NRq1D6^&U4TpHN zzvIoaPFy^IyN!+)$ss%EyiFrvB=aVnWg5zQuyg0L@V$kb`L@8Ge_R2Muociq{>|s+ zJ!}QMZ(KQXJMTmf!DWvp7(9wg@wX^)c7Bnn!|?|c>6P9!D2f4FTz+3m-Ecggt_i9? zmS3dSz$9$8*OLSRGJEFf`l-2q)Sh3sh*(swn!VlB)oaxo-#r+!7=45N+TmTOJ)ZN@ zva%jsUUd&M_N!X_7;dYVjSd^>kVdN&0f+`!c}UeOy^E0WYOfI)P_gH3cHbL9O^49J z8A;;iITg=tsDkXn{dzU2CFl)@=$RCYp-bH|D}yTZ6`e< zX2EyKvZqAF=2KNh*`!w!g|6N9r=}CuE_Mmc6EKu5yEF`xp`C;U6ZHcO-TUZyI9 zyJ5IqY@^zFUz#KoKRak%HrgIeZw{2MHQr5j7>;!th0YMi7}W?hZ$@P7{1Ue}S3hbo zofm=u$;S)LS3c2s)Ni8}Dm)eOc|Ipd8SI!@UhK_Xoj)|<#+1?KuJTjJlcLy31wgcN z)v+qwcrb{*((BZ6jdx6q1Die-rP<9{KF&C6CmMT4d6ol>EGQ+8o32@b<<6Oq2!;;+kbySkOr_yM2o<)3_&!2RRn4M zGEkSl-#W$eq@>hj13aowFVdQLmv|!;dVYo9gs>DMZSHg$5@o?8(>9ki+ThT}Bx*P?MWjp_?bH0F zU1!pUI0tEi=nBv3nP|1BA{|?yWBp0x*b?&z5*ZBL~*PIs5^LNGX^ciYs&icX7L zceD?|?VMa&9y`w0#@wEHg-b~5h4OIQ3x#&i)4_@ucz}MffHCE))`kCAh5I1^k}4Bz z&afF(q{Vw3s&=onOp4>B3qsaHd+nOtt|Ih128@S1V@feUCD;~OY{av?vt=Li^fxuz z0y;B@HRO}so)$zoJF`#!Tx-g{N!@YDU)H8f@q5%u!}(vqRZt7r-t0V(#te0nJ~wo` zRcx`x1Qb+chy$^{KO&31sY{Vc9&JOtKjZfiQ5B>^r5$I_t^S!h$ds!W^_fdreE%4FKYp2efMQ@&-1F#XK$gACOe9TR@d-+U9EI1=tKMzaj)g7bjAN4 z3z&<#fQMewRlzv^DG$fNe<=U?$%1RBv6ER3{P}|u{k$E}3L{!V=n`PxL0w?`yldiM z!MkuF)9zs#jb=MdJgv-F`5z#@GmhLu9aQ8hLsF2nnm#3f;s{k$?EyEx5rjpqe|5oO00i?NScTF?S_yMA!4iKSfv7Egg9o0eftLq zcJb|`5q<7oQxBD{7CyJUyO03LsKPY3SS1{}+IHWy<10AGEocX!Po&{3r#vjR_s_;2 z6k#i7^MFVAXSFA+;<;?Xck1xa!A!3-sA?n|(@potMq8RLeRqXwWGzW~j1;@kg|x+Y z=B3daO;Ku@-NedY5!#QSju)32$}#6jZQ&-QOFv>543?DI{13Or<8GKD@A`B=XBj9!-niT50>EJYdh148HFkg28_&%D)lOxOKH{}DBF1w z+b#P$u0(sJvsruMX`ZFGr04y-P@JKX-o~m#4AmSqq?I7zeOIj{yhfM$+Sh!HieZKJ zRR|s*jpyyP`kE$2BnOj4^K-6rsc5AO7!%LRu<|A9zBW{}P6aYG<*#^ceQ|x(h~IkH zBy6+^_sF?Cd$ytSJC?&iicm1dV1MtGQt7T6WH6};cXRS9YAmZHR}!!OsQ0MJ7gJ>s zqMuS6PRl*tZDjbw)wMr)cYsL zQ!BNUw`K>LGY@-!HeO8>V6kyK0qTD!x1#-I09fpZfhO4P7u9`&%wJSG1*Y$gFE5m` z)x228HBP68d&W|fXSFhQ(t222)}O`AHG1kMGg?%DG7-*U2xy8}YHJuAc~lU$mXCm< zs%ATp zoA|5wW4a}!1Q|Nhb~Km!^Ps!~Rc@LmoP0(}CY$d6gJ7m)RI}ibo&|5$FhiaJi9soGQ)HVZdAI{Q~%E`1-X8%sOoVJe4HY5*RMvlfZ_{>5N#GuYvD$bn*48USNlz66;w1W z-yTXCUteLqeCOKevGOVa1y>~>)H*rj8QPg@H_yxQifzCq9t+PT2D?}p2BEMiu>P@{ z?hX0|slBUW6N6-7bdT(?Q$(#|u9!qYq_skJ1PIP@umpJw|9Bbnw;&HCV>8EoUmGbH zh*eTccDvZF`I@^&p1Lfe%8>B}y9YaF@A#`X^^&>y#J=Fe8#{ktiVYb{WtG@TR+SQX z^{tgI*&^B|>DMmy#UVtz_7ZBnMdBdCRWZ}Zbn1P`VrS`1C(%hi-4jO*(TY>fElC@$RdjvJR2yOykS3 zV(nnp*}%y3{Fkd&<;F4?^}#EC|9-jguVJMAk(p4>#@N~(RQqO zbsNdHegy&bRDzeIm3uoAHj?qJUU~5bg@$Sb9S9j6|8`vRs(&b z36q(8yHcS!_W(XFIV9v3D4`54$_=0Nz73a{krtY-4MVI(6Sqc-7sRk9;M}t?3c?9^ zr0Y}gW!gGL}WZ+`kWV2(Rb5zWGR2IY54{KNnd% zk6sn2)>TRjsMaT=VWcV?g8mo@U+Kj!zDCn~Gv|f2!PA8BRJ0nJwWdq~NSGS&%tp;( ztmZ~<&ufi9d+`(XWbI?nYbpI<1G)E2zdyoD2av)UUdHN;=E_Tty+CQxsM!-N*J(mJ ziYAwoQuJR-y}qc#bQq>R{1tB-^2nImM8@D_?=Nu&e~%6WAl;B`(2b@L2b8UHZGN~# z(akrbF6l&af0b_>rnYVrgpB(WO;-!)B?VH?r09~zCoV42d_UR_^pFxs`fP@kIgJ2<7OWHmKvpszeaN9S(4*hZz z9H&dftsWGcr&>#JU{mW0brTzhnQ93I(X=W|NrZ2H`MLslL_NKIh~7t9qn=!*?y*zG zJRExZR_a7%LSz!a{VZC+{n|fg6JYrZ5A^%DGQuEI6=={u9xq1(2}1ez*D9^P z4<3CS7o4s!Gqr!a(Bi3zV-$w6sr>_QV+t@VR!{dA0^FT#N*gbLtY>e|C2W7*ga0F` zDAxOA0xUXn!#MA?ZPcFlCT`EWTkSkE-)BQP-F|o9Ka!Y>J07Idyue<5h;u&|;}P&V zHIjEDD4hB9fKIdGD{(lfdz~_Y_w8~V*3c|kcA3@`bGP%!`;Tc&Pmn^@PwNZ6vg(UH z-5!QENEjTp0|sn!a4Cijvu1zOvnRRn93lqmY zDit#n!g@%E%jW@b6jzcLu}_b zf-9e^^)piulcXaR)N}LcJ~}Un%@!=Z{U$04I_w>=eslE;EAFV<8ic1kgx2FjA2r zM^N~mV2rH<5Fc``Rq744NCRn7i}Y;8m4T|fV!&x4$QfL@20_P?!hu(gS$CzAaqqMy zM~8~m!_yr@;&i5oiy;FOf9yn@ljSuU2jTXwUJBFo?l|NUSPPbTKi)3!_c}M5eQi^w z=*^QzO2t7@RoWWPXGtoTP382UcG~6oo|_noVCi?|lE1I5xm}$2nEB3_&SUV$+#~7r zndtBP2ZRf+#p&twlxjeg0kQX$u+pp+NHP>EUEk>W8t239R0H$aCH?cCc}kx%#UgJHhVQm3GI}|xg!CnMeMaWrgAQM zc4>y^&u%#_3{wfrs6H)pG{KfRgonh^$e^r;DUSaizP>uF>TcUwkPc~(?r!N0rAt7% z1VOsHLApDnyQI6Nk?w9#Iwd8)#kuF6bKd*B=lgG?&$IV0)|zY1F~=BlZ8|R;^3bhD zsSexWcGxwSAW9M2`*@*!nB>z$Y~6IFsj(mX)-(I4T)9jdmo%1ux^x|y%}TUSqjX?w z3h;a4Q-$rp|GqC4l}h0<4=+TTH=@(Jz~|C02=XdhxFez?1%#vynP)J7(xT}9%WRgH zdEVBKb3Hj!~~kzt{-PN2hQW=QH4$ z{&HzIpS8?o6}_z{*XltV2o@t|={EZPO!a~uHzxJST5O*mxMD$1xuE27mo4_0O1PN;4f+6- zp;%dc$mWtVIw{aqfbcZoJ%uw^=K^>;Is)H=z~Mj<^b4np_Lv(mgC-db&GjK@%@ZA9 zg6Ea6TPQzWd%g^mJzck($f^vU2Zp)qvy0jWz2PFBZuR+YU1Hg~9)FG>U{g3fV=za1 zA~52f(P|Z2X*Ex?Q?h7&SM>eKcG{UD-O&yuyd+oh9*Ohqig+u6OabrgiqD+Gn~hq4 zsPqNOR^=-=ZYLgJ_J3IZ(4o13IcGGX_kH!~=be}}Ygn0r<1uIQZ2211oXzntYc;)0 z7cEl<6FVP#gTF-xqlLQj8Rd>?Vn44FAp=AI*ZO*+q17EuTTp4Zqyy}rZS)n#->R4Y znN)P(K6CNHzXh`1@jho1ymp2A(K5gYNglmhsO7$C;=R>TbY1jTh~9F?eeA_!DS_z!jnQ-Rp9IsrRuO_(1wD zhSi11s7oiZvx1&YMEP1Pc~AH?q1(}6m85=mV17@RozA^W)$Y!iUe8GmpYus-ErW5? zPN0T3w&4NC$y0U4rz~?_T8( zPM~pYdN=w&7x^zl!RP#$QhHuN`uVR1{r_nU)T098Beo_D{zniBC_9)0sE9h?*-bx$ z33}brtYA1DkH>cf*~h$lMZhl-ATtfO?!e;t&kH^EehxNIvzToE-x^2;6u8yzU|mU0 zqd>Ev5=)q59ZfQsMR29Ldab+18*sy#5I+=vG~D&a+kZ7Vee9pF-oAoZ{I3$q|9ka7 zNIj@(KtoLOeD%Uid{l%T@U@mJ(SR;!EZljyfl;FRjRWuuS4jxb{`GvHL_h-m_=%7e z@n8S+Hy!o!bBJdJz@|u6(ot$o?W7NZ_R=L-_r537kqi zKi?>93}sscNK#R#+*79PAC$6^2!wP{0CkH8#kI5uB&=QN*{A6L^IZP<=XxrX*V7*v zP?zO^T5}?)Vr_40$NJ)NOAYO5FSYyB*;53}8&1T;_znMzCc>APVEm;aKoADjCZV|i z3*M6yiTXGRCWkb4DBeJTc30pER0R`*{tp1!ckG6hwMG@)vcm&&y|C?Mnv8z;$c*O2=YuRx+jGU9|fn0#??)c{zZz zergV>koi4VVr;bwApsc?2vz@+Z#(>-W{4!~;LE)`G|Y#mo#A`Ju)c2gh8(t}=WzY~ zH^X&6X%n|m%~4mpWPvy+1QhZAU?h9U{^#H=l8)zZ_R%ntolb)DN5+Un?~nRCLAe$b zrMH)Uvoi&r_T>S}?b$ib!{bALl6bJye|cuEaDY6Y3^G3HY*A?H#}g(rZBa$;v#6#p zotqWcQ_)9f9DAc^d8iFcCkQxo5%36Ag+>3pq@QoZ#ttO<&cC61P6SD3$WPiJnvI7+FW)>QQ-f6 zje@QKetwUDL;E_w2~AMZuccT4L@PNx!N+VAYp=t>qLMu}`mLw%eqr);2mI7)cUV=&z0s;jp?coKb$!SM?@kdPQBx-{E;bT!WveYZf zef`bP=&NmyQgiYid0|09;|rb@yK_-OpQXwEdRV0e`P6t{^P!lAPYa>L;Zso@=`pX;r4^>DeB`U3fx^x12Vvzi(o^3wZb*_>qMex9XaXO*D)sk$z z6qY80&A+hKDEIx95?VYZZ7P+On0~#iqrm|mZ|xq|3eVGOC?3sUbzhNE9U9;72;->4 zvBu%@6tJQ|RAEo%e5VYqG@PYUhK_Y{X4B$AE1# zj5Dfmmce)9%Q@;HT0SRuv&TKo^B1IlUbI7_{m$|$#^4tp1q^`7zc!M~9M4MKVP2am zb00&V{BO69dddeWg&|(JtJsZ?v_2i+e(`Xp;8*MO+Si7Tq?~u#f(^o$Tex_hS1`d8 zb7i~fk72-wedbhiF^WeuV(nk88=T13k=7oZQW@|dA^6ZF354)^!-+)duE3V%wLc>Q z(Mlqp^uJ{e>kUv}px|Y{PEqEqcYZ1I<@bDyJmz=F-h|))I%#1Y*Wo29$Vd+#bUs^k zvvFoYN@Rtq8_bgqZeTMS(AFsKP9F^w%$J;xcpfLFcn|A(>hZuS= zk;jk}5E75aDA9;tr1?t{ni{O{`{i1g#2(OO82^X^W?+AB8k@2}zf zmAym=J$VTsBm{&9RLST#2vedV`M4DmA&2)pqvWl;_tweltX!CwnBwWgl%&7DVeJjU z@;hF(&!-36dH|pSX^-_R$7X;Mz(=!&zfKLNfPt6=&+46Qtepxa5^s4sTX-2zaO49$ zFyE7Z_-9ly7dSdqhD=!j??(gykBgon|MPcXRJ5#Ou6Rxo^V;|shH;wldOQClnqP}@ z#`1y*D2mxFXAPJyU(M=vZGT8OtWpT44qwCiL>BD#A|}`C`je$_ha!AWC|V49Z&*p- zHHhiFjHPlL|1I&dk%iRopDB(e^(zKLH9VyhGTKzmd|m@x=Renp1ivt?qR=Z9OPrzY zrW>OGmAH)YE9j_uJGj+-KEof;)8C!lpWD3yPJpZ~SF=-&+7%>r0W#@0qt-a}Qc1yQ zvVuj9%fju&;e!1M`%bOnTt%7~pd98Sr#dNp7uia4BHD%_qE&jg*zoBqm_YRr$#SuJ zNn0^U0r0A$a-|iOgf?_f8^ql75AG^A|cP*&A;rj|7@voWBbmt zk}GyKDo$q!dfkfSlsA{S2qOW@TvRne`}(gwx$uQ_^^pojcw!B%M3L|H&zE;RzSH0Ev%-a?au>HOYmtq0zwipzg{&Vb{QAY2&e)o8T> z-@)YaI&Ng#5Z`&@RM6mi{Rv$(_8Z@h1&hSq91xhrxf=sir0hP(^)V&<(oQ7jZ6S`P!%?rUz6@n+81#ewJdvX`7H6hB;+b##$rlN zWzubycf@x*RZ^a<1=(8C_^lLWuxn{Qk$~n0x5uu7W7*C{x7ktc$rkXokK4~$lxo~u z_b2l64|&ax?NH@$nMGfOX`S<(%t;dLwX9WWWO<{WhW~G-Q!@XYb8XfI{YMS-uVUzb zKKOh>mrQ!Y6krdZ5954(%;t8s?-vSN`{Gfu%=Lf00KnZ__Oj|RquIoRyLT8fe$~j< zbo7Z8mH6U}!|h&Hv(=%j$#lR@`w>XyK73i(6Y$orFD=7ux9zYzNWS=WWc%7o4j)y1 zub9v2wE@wZ7IEd3!0cl&_1cXNI532cUZ0HhrmVT1TyM5v6Z~pfbFUG$L_H;h4B3*% zO`aex8r9BhC7qL~`0x*H#NVFD{S>Io@|dAG^s#u$Q#&WSqc88$&A$bvKT9{`7%&6( z8lVU^UQlX<6is^+W#dDA&))?6<%@O+>IV0fuFz|Vk2;8qsdM?*a%PC;%HDvC!+WuR ziC=@oW-KcKx(M-qfa@XpY*Kj&V=9T4_Fe|PbiCxn#hy&F=Z+w#dJOAZ8DW=W$i(s- zOkqYdV#eb#wMfd7dR3I;r2`U{)vWgu`;olLdclqT`n5@aC)(4-%L`v^3_dZ@4)14 zf4j`xz&iEuA%uVyJzqBFohqN_6`AMVm7ICkrhxNkDJIQ(64K;f*M9`!jM$8&6|zMG zvzYb14;s&W$d0FtRWdvyx*^r>%HVgD$EtG5>2Z#bC%CO9g5w?4&d z3FDhhV~HFB0K(A`GzRbbGWI~jE0QeKxajkxon3{^cf#gZIhjw(T$+K{E>h0NaGmRHt`_qk;3jOVEeY^YR7@8L95MZZ>g}>H4Jm+&zxgSlj zH!AXG*2O+aao$YtJ`r10zHh&!$r!WWsaT|#zdAn2eB`-^u!jDI-Tx|pV54R2)s!#> z$vQpMWI?dZMt9RwW{8@#KsJ}P&>!iwU8uhl%Kt7mIu4-7#E@i)WBZhlTc1u7m~>DY z6MFznPzxkGED9YJDDMrZU&*{wEy(HE4%&_9VKB#ie?%wHzBT`3+va%gHeD*kIMAs5 ztKgzz#$5=Hfn;~?TROV+F3w3$@j1K4w70`a`!_+2KsiDid>jNKe!v-9j2(=pqk~cq zL9Qkibdx3*SI7nUKVBZbM2QNm3}ebHHthBB(=~U}AJSJXT-GaM7zM*zs8VTy%~A+u z;Y%Agch|(UdNCyh)ku>Trf-?k@Id1>0&jNeDR%*H$4A2 zhD;GD^!`H2rEA5gc5>w3+~{QQBv}KuKa|U&p&Wb%WJNsI&%AQ!EWvi$Ki+}1YwqVV zcQ=!6?JpS+VW-Ed_W1)_u!Cp22dyq=DO3+!W2KFx?VHV{%^^elCxE(m>!f&!zai(C~ z=JlTU4Y44;$6?g?^nUiYeO+o`)Z^Gs-reg2N=1Vhpt?T%Ay_?Dosg1@HG+#I{VtI? zg$!D4xY)_hsc!+3(?XrM);IvQ1uEubia($P&U=GMOk;_YDLN;gykB1Vo0HYndLRE1 zHQ-Lgopd+a8o2Gc_m}(GJ$c<4-ng6Jx5NUL%l=|j6E_sfx5bZ$p|G!r3d=!*{bvaR z9yI$#o?>;VYWc*ZU&k>0SeDs%s87B#^>>V$HXi2=gYD#GHL zb?sbfTm-!7hlUhyW8+~B(VCs+5aUa~@sqX0BR9m2X$d^R##Ge!z6l(RvCX$;6ZH*Z z5D5ZaPYgmqg6Cx)Srm!GZ!Vp`qsSx6`T4BC8%kxqhg5rt!HVgl z41S8s$8V??r@po&@ltySb0?zO=fa`L^LDU$7L)}>E$vig&GUWomVVZtO5E0&7}O(5i;x@U`9!LWPEQtpv7-n7Yl zxoi%Zv5dAnp{_w3t>O*yhrV=70ZmM6#6q`gV!0zNF7(6*GDOyHYyF4)cJ=Fd%r}OT zGuJ>bYkGD63rIR@+qwuQ5r?!>u(Ic@S3?BOBc#<#vaqmb*J8(8HFEP3808J=*{_d< zV()luK5{MWKfp#6>28g}bamzgoKGgBwHfiHHzOAa+(i~~UP9E5*P0KB(xzy%dI&@i z@#z?uZ?RLRRpXdr&kU#XNZYzI_XI=*P?m%7D+8=&A9dTjWM<48u0DE7FR?Me0qJ@o z=BlkLHqCDph2Ub)SGR3)q&JqHm@QknE|wm_PBZ+yKU2m5taw$`G48~~Wah9=vKva$ zig)(A`=eksV{}?jq++6~W_Zjq7>^~NMjOPqXMhIOjP_ljMS7)hRSL|V{}ST|5|jwy zBPM@?v#_d0Hm}ykTH6r@Ci%A#^7#qhxCU6Bs%jN(uJw02tfW~EZcjrn2|f?R%fr=+ z4smjqNsp+)7%)DZAhhHhEw_ivqxc4>ABJFF^+w@n!Mk4_35ln>BRxA)6tij|TpTUP zw~{O2-E*F{?B6*zyu6eLY}an!tAL^F38V5-6-u^^&z0W-LJ0%^WZ5&??TXNHG9|tE z`2b|rX&1rqpQ+>Jp!?yxShj=`?5($ePNK!IWj@`AOVm?w`@m;0{lRP{ryaAgYCUWP zl3Es3!Bc`|Cs3}1?!eoP{p#j{?ULZOui=Xqks$8X!HHL98vY^-#2xyL3{MS3E=kvI zx)ULvz1F?Fy78sTH$T4rQhxcIV8EeMu>2Z6-1hWye`0(L+Wb^#9U6V-eQ@@m_(vxS zY*#|}i^F$N_rG7{8ywtxD2Pc#?F@-tV4wacd|^?tl^U?{rh5`PPO?VS{`WLAo{(!f zDyq8EkP*Ll{RdXdNz^^a5NKF1h&(~nLVdm46L5PI#gUFhHo9Bj8kCuflp?TUtimr~ zmwSh1aYV7+ZhXME4PLCZmD|WUW{yZ@*HRB!q=$y}kQ}r+R1fbEO#&}Lx$O2R;I3*3 z=IwACJ-!^?oL*?wD`EjTkTGB$_b^Ef;q$kr%nT{RZUjc7%v>WLkOK`LOOX(mK0v3< z(qQw;F@2xY4o43c^EZWo9N5%8I|ED+ZWp+6uqc`DgO(HM;5U^w9X;(k$ zceOuAAd44~HYFORwk#J`pU9K!le}OP)fa5-d z(*$qc0_>4NqkAqT{u)?2D$NY$t1JR-w#nf;u9)~lb(vt(`4j@Z-l1u>N<2f;OcU2b zx*Dwtv@U^P7z;4x42t1mDmq3UUGYuq)}7M0+d{W%SdW(6b{9y<1e}WSpIlEWNs?9u z&Su2m3c>TH=$?JtSc@dm(Hz{=uuoz$tsCSU%c|RAxjz1R+sf7KWFej}jTt{u6>I@y zb1-tL->eT>l2&#C5pe=PkLrpeCY4fp^`9k>dg9?RGSE~@#(S^*#wnHuJ=pi3#!vL* z?gvirk9an(4dx={W{9+!J^Va&ieVn1Tkr0S_joSKtf9Af7Wj^n0Qi}KxcCwxlkzm_ zx1o&Hc<)BsJ@K3k=qCRsa`Law+%J9k7h-Xd&QGNT?*f5W3c@ot@t{ewwsEU4I_qkw-o+s6}makj$cQay?6_^tvmjY>-215@;(`bQs60O$p5=N+VGnJaa=uqw{dA zHOSp27g?Pf3XtVUIxK7V=A(cY$6hv%oJCin9)Oz|N)bI(j9Q1JISi@RbrNs8Z2_aZ zz7*GV*1Owi%;F*c?VK->c&8|(u5mqPw`!bAMJw>RNtk-aqKhTJ$4Fg;*oNo0NQV|P zD;2y>YEor0RG>ln2`7F;^K!gepzQ$HW&;=;O)6@`x#Q;D9u1Zya7Q{B;rmZIpgPw7dKM(AiIN^T7G0j1|jN7e0T(`P~;pQ^*2bd6~@p5fr{*=cyc zeEgmRpz6KkEkoW|1bp5;eZrSIN6Yd3s8Q!cM$wiU1wU&l$t z7`OEi@@VL+K0MT1@m%a#LvxT4Bizh@JX8*L-&13k3`WTz(bz+PHs zY&c&Z4M;CXpUKZ22!qMYX$!W03d8MTg~sC}17{5gEWMs>YB(A96@l4R`~kUTIKvxY zeUn_-0620Vg8(q~I+>JHG;*-Q_<+CBe=b#eu_6twT!Q z+*S<7ucs^Gmc3ioq*Y8P%9y|6i37fx=>`GU1WZe!Ax_+4J~(ga(txO^v~u?s)$T`n z1t4;mN#SfPl#7eS9>ZszdOnVll%KWC?pGBecbf0)5Bg^95U?wZ)`{en=o7c`lpC>^ z+iiU>9eHUnwQ73M<3tP(fG(7OSQ3d zxz*`a)zBzm%PWKfneN(xGCRM4fHv~9$x`Vv0Bn~e?(q?m**)z!CioakCr*hM5d+(o9!XhoXiXa)1z zx(EcIy6fk-;EZ6MP#jG@aVA$I6(*QZX2H~1t_W)m4}JY!?b7FEuF-{6@pPm59^-em zkDllC-MGUz1KbS>V3T;DGNr)$z5fsKv-1z10bJp-1_>18XZV|52FxQZ_g@U@hDTY3D0$=Cu=a8x6{_Tq;nmKDOsZNDaH?u*1yR>!x+ zFucHLk5nhoKQ>!wCE_qw3KR6a6>UOPRq6#PS>I*=VJfy__-jSesYHQ)$S?Z2vl>zq z%bf6SxevF?t$+BGf2eoRH?^hK%0L)AO1Jftu{D3EkhpK3!C`5#>FXCHue2opAj^qM zX-Bb0?ho&Q{Q8aWtmS<9plecs*$>%{76woyfmLF8XgsP@AU?9}Z&eW38FV6XRZ+kq zdf0z_^sHtNH6?p3V?f5#`uPu%bS$@^o8w__K#wsosdl?T_z1X^`_r49DYyp zsDz0rxH~f>l;hN^M`Yh036Jhh4K#eb4AV)MIq~v2sqIe z53)yno7l}WVB zQZa9s2uotVoO**h`&6F1ul#Mg!I-Z^sW=|96^mX+LZ*KI_^XZdcZs%hBu%{Vx)XVO zOLeWa6lZ2cy>rZxO%e8@%QpQ>3u#oLSGZb?Qzv>(XFn28WObe?eKBuW8155j)btu3 zJ>hn>>ZLh@Ybr?LV2* zZe^Yg##>Aa2S79z!%e~$s>0%QmIpw znaM2fxK*j4)&Ii%lW-%RXD&|DTMC^TldQt@jTu91&F6|rM7Da0el=eM4y0wg!FMZ} z%RDuc-f=KPn4g9$fV6?K-s|%EL{DF_$QPadwApfrCDWiUe7h4ecDA<3cG1r z0NbJwxW!4I$V|$x+1T{9^nD%fO3O(}bM>#L{qX`cj?$CQ5lo=bGkBTSWGv(On|YmZ z1GA-=pM=4=Y96^SFcn4qy3kSHT`v|-gx-_EVdQB_kEUysNttQX$tM*vb!C-3ayF_ zQ8-6oEEJs4fGZ0vs(=t6g~hSrOB!kg`rygduU4w-;k~MwM)q5^u;!LtntHR#3+dXC z9=}u+&9B2t)}@5;F|<}`)k_mUKIzORvw30~4WvafX}5}B;o>eyLq zXzo~$Hf{3C&pA`Xvb4`xYb|}X%(}w4qAft#?Ny(BEv9piHMGoDokWJU_w~}JVUw3o z_s<^ZUfqOke{W&x7Ez;_Rvzwjpw+DJt_nppZPNDw` z9G{`!afZy6F41-GP30E^*!K;d{xn4d`Uhpzku%=|^4qJUR0i`spSz0*Ijiysf0dXj zj2-ovt0A?Dy^++0>cfkHZhz34`+0i`pHMdaqvWv(j}_#E_M;r~4=}j-X7lSxC6Mvq&)s!YrK*D(=sJxRA!I;ZsCG495Qw)p z5o6v_-~rDaA4q5fimNLqeag|5HYzUTA^VvoPvt$;m@;pp0TuZsqPh!O?DX2D5gcf48cv9lIF>kYH6Uq+(95-G>Gu3){>Z;F)k1`+p7o=YSi{B=a8 ze|`y;-D4jUb=|9xBMe5pmYQH@nSzUW+P8FrarE?*vai*KVo#7qAc?6pZ*>$C-gQt1 zC|`NNTz;AjvUGLcKQPtT+IPQR`4*yj#F)0(0nPTd^{qL|d@-jZF^X1gfm$(7kWWft(+?sutam=O<)AV-g zSek_emuE9cH_XYKT#wIF=y<3+91?U6e&|<+LNvc!_M^7*jbhUooBbr2H@22xo zX*tKa5F5Zki=^)$JmgZVf(yoU4XOy0zb3wFY_L)nuKFZas9eab6qmRntJ09nnWZ4H zLG`T>)$txJ@w5MB`iYeRV^~7?oBwH?&j!OXz!$a-4Vo+x<*XW+N-bA`0x1-qx&Zdt zz0dC>B3CQQTYosx71nAk@gva4;F&2_QEbd}XV#ulnTtg}ZjFjM)FNMr_}|R!chJR& zsL>GSnM1{HS*i=bzaIVipN%6+ytjmbOy2aP9@oLUk)?|QAT=x^n?1yGs^YPDucpzT zCPq`%DUMvCd^c-lP-T~pI{9)gNHei@XE;U-&Li-LO@P_RUgt|S{Qxr_u~5tm0MAG( z8#A!eREP~nOc9k1B{GS=g>i_{;V_$x7)@&_ax}kOSp^CNmxZnY(fOngP0XdrxsZ-W z%U|XfRW{H$_Rf=-VYX_1HKlNx7F9OKYc%qB$^^*FemL@n#|Vr(cCL}+bolu}v&k)P za0>R~;1FvAQ>YsNUfIuXq`BKGA!VpeW^>8mIvDaqT#}FNl`3_he<%ITlmF>ntb~?c z`_}gB67}tyK7FULL~3VjJf1whv-`DHR|V*>7NP4zFSQHHKY2ET8Dm*q!?%?nQS&79 zoL{_6Ty2{blmPMXH)xVFj%8iY%jAX|^XmbsuDxirkUZe$MQ5#*RB%U8^VJ&-@HUE4*@%m+cc6mE!|1oNI&= z6x6$2pEms5d%d5#>j-luaYn=H-1#^>4p_5aoDj@K3Ygkj*uWvE``uOI7abScnE#$% zMF^Nu=zGsY8RsEZLXT+4Pi1};n)<`23bhG}^?)p5Y4aYOB8rRb10$MC1`|E?5n-Tf z<22`dyKJP%;*NCIsq-~7VsB!LdO1F5Xqo${lNV4VYR&1#m@6xnIii4oVk6s|k<>0v za{xk`XR zj>eBxY>&<&%AdZGv>yzGcvC$z_>Af~+z|seZW9@Jj7F{0>HV|oU0}*=+G4}err=?% z?(T?;ll}$@UaD?Qu(BTE*+Jl6=_Qc{Qec%y zX#Z!Fjp?c~YZE8Ep~Q5LxVKvR`UN4C?B_!6#t^GLv;G?*Vey;qmsfO{uQ!j6kC}S2 z#X{fOG9Dj~LVGbCAIn#5GKoEY#c-;Ta+;D$`S5Y{yw?5d##OC);Ez*a*Nd^LN~5Lk zB`Z^+*<;Bf1``Pg)7F#FOnOaBNtVl%R2TcRnuk%T+_2cp9O{BGh#x5c?W59d&movt zh{-}U>C<3kk<+0_+t1semZ&V9fgEQ(>}uwKrBEGvJH7~$K$$La=fs3Ff^vZeo7!+3(=wPwE5M+$P9AvVxnbmFVg5gcx|dH&^#)TD%QPC60zA0&Zi%lWyBXQbdr0AYYv$gX_+bvC3 z`3E+jpf?E=?FPB@+aagL#P!;~J(}5A)L*N(cx z!L>;3G~0r?lDjEx$xrpG_P7yY2*<#_nm-(x@DN>M#z81Hp0WHHb~(j>o34FrrIFyA zZ7z)HUaZ&zWG`%yb`7PbuDj)%X~;iz-G_U9=Rb5e;tlIU{oexxx&M6CiVu{GG{nc) zo{-aaK4Poo*0Af%F`C*zxQuR(@i#N_aja5&nmbZ=vk_TF=gp|7r#jkEQA9#9B|nz3 z{oG$Pp?ciU6A!AeruE009@&ePhsR{W6;jLvOcQAswAlCCZOp{XW!Wt&X^*wVjaEU^ zI8ex#pb+r~($#4WWahf&^4n&z^ehEPSWWlgxOhewoK>!XVuW^^U1AS3H6&1!%*$8Pu!A%cNow zS19TD`;%OVJQ01Ytl<4^N*2D|t%rUoPh*-`UoX5)apduXXMv#VfJ%OW@exwi=U;!o z@j(FupI;T2168lT#ux|lr4ebioPT{$wxN#MZM}^kE@3?dp^ylsrVudVEKAoW2P#y(v`Opop=ztT|cb0K; z2C-klVYd{oir$wuRtmErmMI=Bc|(=2k$l76@bS7hS;u zVc0PU9Wn2g>w8%GIV-UwE42VEm}=gsdX=|DIyGM^x$_;!X^>ffhdm3hZF0Cqh1Nk| z4gB?u6QW6qTwkOM>MUDs==S|LacIS~cRGH7;YlT-mgfbISz3kIWP)~w4w%VD(00UU zw{@M>AyWDggaVc(A+Ygsni%qmO69e z+TGzp&?(_!ZT6qNfxcj**J#zBbpO#7cMg!sHVo;aF)1Zqj*1~E5?{H(o^=BInjIdU zCdP42`}$gUOY zs#47sWCV+hLFFm>n`rvo@bIf+*k6P9jH7WkXIpSp2q9l7=!LdNOmx(Ug*?6h>_h|$ z(<+haGO38uY#B+t-7eP^qiY{MgqLQyUBwm}Y}u7_AOD0c zV~r-aK?&xVT)!mZ8b}EwuMp`DW_+3}D)2g?K#chMrU0vJo4@hBoIZ(HJ8iFDe1VFP z*$13)`+o)lqhyJBZ`CYUZZ@bL`|oT!otA76I~Z_Y8}W;2KL$dF*6k5qlh{S)OISLs0e%%z97S zpuHs!T0tg`g!0o(6al1AKBTJ{`8riIH61IjdcmINOgw;wg6aSXIO*r%*LHXsU8ZYB zFP{6A@^NGVh`-R7R)73Pgx35~c-GX^BrUf7V`6Go+N=oGa3JJ$2q7PKsU8nv1M9D! zK;$4YvB|M9G{$>?Jj?Y>O48O5cHL6T$)8G_7* zcyZODW_KivF0SHmJDHvHR={*)!PNQ6u<0O=g2xL%m&8okLu1xe42MRBfJPFcnauHW^uDVo*Oj1-gK=^jawcb|Il@?QDP>rDZ8vm6>0^TdmseOL!JTmen?Cft2z zpB-+GZ;#$~DVP)X<&s7#7*wdoGw2k-^px;2I)m-vjf7Q4;b6 zAZ&{vDt7;hHQXC?&gfiBNi)G#Y$=z#$!YZlpcKrQkX}tnpbmPa8}cCx;5;ghP+wap z8#Bf0=q%Ew<@A9fn8f?Di3aWY?y73zZZ5C~v|R2Y3C;67|9HfY8*JrjHdjicM!G?v zk)@=&1(=VpQIF)Zr51aCw6n$i$kNER?F*t}#X2WRw6gE=FsR3*TU@MokMZL1Il_|H z_(CwCfBj)IKDM)cm8^o*vVCdUbiLmenZ}rw69RR4u5iSQG-?c8_i5`#?Yi5s^Uvfa zMk>@dP+f^ObUKUE(LxHG8${P+2v63l&E{Vi@dWi^zv-fbNO6G)rXfJPDt25z>XGp{ zKhp7g8DJ*R(|;aI#GNdZ^37~2KfNgyBH(s`{O;`;AGT{bha7N!0Vz6CVifSLePro+ z^Lz`ZHp5k;#@ZaaaN1v)o#r-!-A&y(n1Ba8Jc9*aFS)Arf&iIOh9iOqmR~!3EaHQ+ zREK%;;|4Uph|C9q|9e#a&$;Paso)c!-1q567cDtrBrAj*WM`d^Ql6ct$wq>xJwY}p zIrlqd_Ayu8>-F)^m&Ao{Ltkq2?1rd10ca0$AdV7S$V3JZ{y4yRqIW|AEgzr>lz0{= zqjmt_tta5Fn(7M$dTF$K$XVQWSLfSAYncO`^>VG2H|*tlFM*3d-l1yaxK=O|ZX&}H zi9ZL`*U)spWkWpBhYe+qqS{~^aV^n8$bBVrn49LN>Xj zu{#o#>U>8Qe|3OixPi$!UvnLe+7d51#DhjI9^WjQM3Ln0NI1X|l6FR)v!kszD+TaR zhvRX~&?lYu*x8DLAt-<-%m@`@_T{Y+K4!&-Yzh~YGt_|tP74XNpA_e0RCm4+Q;MYn zgnB{s5&qwJDJ9S|)5R0X`(<$wFJxhY_}rk0w=W@=002Q?u~w@d+_d|sR8INqO|X>%ZqLHoCw!f z*)3Y;otmEJ*Sc+UiZ*bCCSD>B_fyTU{0=la7g-U^bZrE#VI9-7GvWRDeJe@Ra!#<@ zPWcElx>R7$Wfa6G>M3&MzLMc09nayuBIbcM^d0Y|tghxp7;^5~J`ztlGAxS~B>vQI z$3`nL#?I!E1fMh#pWUzm)DpfD*RxcX6Qi+M_&(ZI-=N+7m8az+4>hvr1F!I{lb7+aeb#WeaXEd>vhGg5R0xEJ#Jr`K{Yv%0rxEDBuTd;I~-W5Uwo# z5kmB+T5GNP5ksCkRMkU+7mQ*(emezdmFpPI%y#ccvY|El?VP#N6aX?$R~#6mW|Jn&b$mb<}Rk*+m*0?eB+P zXS_qj!MXoIxwhi&RXxOB=av4o-faO%e_~@iCT=`?NI11QwwxX(DVRITcrz_p`}>|{ zuzF9|-OU#DBWa)TiTY(Cd5rb`hk*brUb~jthpsjz`nf-#0%6{W69&UWYFS)k=8!k|AWL49Nvz z;bvdROY#X7-jD*dT>NwL{;`}6?(E9_cbj|XCOT$h2#>Uqkkp}fU^@Fbc3J0GAWqjW z#*jEE7Y@5+*+$#*dKcf1n3|TM#LTanhXs+%XHR^bPu8-R4u;1e^rA__dI+0c5fIzl zmM9U`6}7d;t1ZZXq|p*y9P){(r`>*GE;aFltx@CtwTTY>Sr#vp8P?;Aya2!HAXRpm z7XK~=4-x+j?u#fdFTb7*vM!9}*t>S4wbhPFE`@bOgdXm{Epa|V>ZiT{tFJ#QKv^7{ z;1gsD)E9Gz{4fRaABw{l5h^1h(Dvdsp#>&pJFi*;239Hnuday?)Jmc+A%I_?$!q+m zPFtuq#?mN5IC9v?SNmL$PA5F;jeSj&&jpoFI@FhgrnS)xTMjDJP&j{(>KQ+kN0Ygt zzm8(c158+|8ghuk-HkR01b1dz^T})uwIUYs~PSP6pR+gVUsgo z+CVK~e%qtJ3H?=-@K%%kXd@97a-DkE*H((h8j<>gftMKpRZL^7to<1xx&)A-rGUdDR@qdTg}GH)b-$Xg{#3zWqokP~OBX}=E_34LI#2_#7#yly%_N!08GuR(oN{oqbMbZkHNlXe|3}+fMpe0XZNo}PcXxLQh_vJ)rMp`i z79rhT(k0#9(hX9Al+x0zB8?yt-^spX@BO^{{`vi642SEw);eR(V;*7mrD<)~Em)q9 z$EBgAE`7E{O~mtsO+t`Lfdq_eQ^(g^+a^0jvwuF2`ybwd2!s5u2l8kPjVDx$KWLXP zNRX!Jp@0F^x5RJp(jf2AWeV`?sYk1mQo{51srU9K zDV&L=|EQR#WGP#7eoHvVF7x6*Q3I#gvPKj6Vb2dfslL1zORKF=L#KEllqKX#zWdRL z3MC7K;UL&Pw`*Z^WY1#O+I1{5=zj6d(4^xj;tho!vr@vlpn>r&lsoD< zi+%i_7!>ct-rf6_W^h5c_SZUKC5H#w0a3RP7<`*t z_T1lgVU%2D`YxyIX97cDH1K$ za_jb?>hq=W)eC|sE?A?VNCAtC?Et0cy16s>pR>Aj<7)oVifc_3`{$!+U;HveiZRJ60BRdR3 z;!6<1jfwg)q5bJw{xCN+`*v| z`0(P82u!TbE-&$*E2oV^VG+L2%>pn!dT+$|lGPw(nSSf-n6pBGX&gehhZeM3V@0SUu<2qI3Bm`<>koYLh8=%#Dv_Uhw zWv##makyff^02Bw!Wob_N#5wVIGxR0G-zN}Q8fRjQ|8zb%XlOC#N!=wbR@{^rD&4q zF>Brp+$7#2rDwZU#@1A(Sk1K*&lgLTbdzjX8op}JrQo7{1=fCqYf?1b`Ux&RW4UQHqn#0iwDMG?+-4}jXz z0Ucw}HSH%=S|#M1+m7DdAKoUnLuAhUOq+tfxI)L?F{yd#?Wt^L7*(-6 zK;}5!R7#0H=P&u7L!1q1|QDUt? zm~6LFPf?@ak^ga*@1O3~$MnbH*biSx?|;9NfHMMRzo_nxEAtT2K~tO9_o=9QAoN4BNp4k%y zqMQ*WWoOhWEBT>X$m326NhS(}kRR2#ZXXJCY}s#Fkp-Dda^{8k8h=F=yvA2zGKMDq z^~>r-g3J27d=&E7CFv#Q0Ad}Kd0@(A_`h%ZfADbVqleu})$pHn8{k5q9MfM;Yprjx zU%{HORmUqKFH}5dXoaaT-TGv{4GzPxOA9q2R1KMs^luFi6z1{Xi%UV1-ep=GL{3m_ zAakBCbN=(v=!GK{1z)#R1gjLccffzV0RDr>uP?*n7DA(*JoVFZh`{4b+|@o9pQ6b@ zW-BP4=YYwxeEa#8n#D#k5$5XOAK>X958zR^Op1f3^6&sEH_QD0>j8pf9v&dzzgNk6 zCp@m&G%i}+Y@MrenKy*`;+WR@TvX(KYgm{_VLpl|28V;ZN!W%B%3FZr8T6yh)ET%Kf(~54P)3Op;z8t ze0Sf!xwO9ec@Zv`$(p4a*f0CHwS-Q+-nr+8fEJ%=0+S#1P&AF_uLu5ZNBrfFUi61D zR2(D%CBP4-2hcy>W6RSU-duvaws;as2a# zAWJcSG*XB#jMq_d{_9sJzBla=ORz17jkmX>cI3sc5H~iKn)INzL-zdH zG;_cPjevkYmJAi#C$LAHY9{yhv1OVgX}kd1yt#L8iN4eMC3Pmu1S~oGcpGY^?6naKBm{<=(!9)e=@I~o|}slN)p_9%S9lV?Bn1iHISw|~fq+Ug41x2$Di97NBD-Sf+=m*)Rm$p8J}18)B~wTAyZEpA@`-g^N`3Q5&)QELD$X)Dsz|QTcGX>x~S5 za6uT5pEEn7O^d^y^*2UJP>RC^lZ=Crv&7XgR-Ve)Ar#n|MxSQ&!X-aMO~Y2EjR){K znMQ5rilF5_PAVA>2f4gV(RT?a2SB*E>4`J2oZ0*h9^|v$RP)8B)cXpeU2X>&ZW*7= z3HQw2BqzOnwu*Acq^V3h{VM&v{tmcZA1?f4i~&@`tg=!;!jJ|L2j4uUv&v+JN^4c7?w9GwJ7} zbk6!Qv{vZ81*IC;2|S{#Sq)Osf*cl6=57WnJaRy z!a74wG8d}$Uf5f#z6ZItcx*o3H|;UL+KmAS{7@?nfOi>+{B;33d@$-VMr4{i2)yv$wx{?olZonHp7QE2ad*JJY%KNF3E7F z;c;22!bWC8F_Hb{9GFXzfnLM8lzu)QKX2fpO*P^G1#3TR?`|!ZWb#<)q3ahpDLc!0 z`+siHf9;dNwQ%qi#0)3?^$oFGq0FB2yvO>9vM%~PYD4i-TI+WyEdnJ2ATK2z-2HD9 zr%q!jmf+YKYclw~kv^usqqTbD^g|@J7tlxZW%4@#7s1nNvF4J3_r90@V}E%sVrZ;S zSAXFYr|y_a$i<1?M1)$F*uxJoLCDnd0Zu&QVTE2JLht{(U;oeJA|gMm(64`6p)drB z(*YIkFCVAqw%W%2a35LVIzp<-byM0?7uV#&`#g>Y`?(BnTq)*2gn>>0i&d zC7ab>W~q_8d|i&fq@vy)$w6}78-Zy#y6#)OMPO@pk34{BbJ=Q3;iEEqCo?H{nyMB9#fTiAb!cyWPj~@ob`xWmnZ!}I0?Ovz@fbk5O zk(-lKm_aNDd4t7x#AP_`deq*{iZ@2!E^rSrX4gbxQ@+jO=aLx3m8J5zI)?rl1*vTL z-ZW(nxNn#4Rz4SOa#nW$vqTP_&7$spxm)=b}3AGe|&9I}9ih+YaBjH+@{$=M#p zXfv@L|6Fs7QM;0F|749?>7))BtihJ+jp@eiGR0(W<-9|1lbPrn*}crd9Hc7rcsfn#9bx?`>7tAb;LuR^3-IvxU%RnR?x7SF`m+?xg2(mkX9~!DYHadcCYz^y_p0A{ z=tnd~Z^u{otRO?Nz&{{%X3=+-p zK=ty(zE1c``VMbVevsfTNJG$2kR(bX<9daDg*Ws|5Akc4*I_0T)ISn$ljN zGNml*FPFoU6Tg2!M&;FL^=WG0C-60TYBpz@-T^Jflw#r@OJ;sz2CzNZU(uSuAYr1H zZ#fR@{drcG&rXT4>q%m>yp%5D(cUyUvj*dvpwfs@hmd1CkW%^3yC%7wWeYShOLWYk z0f1PWUI%zg2eW-~dp}T)!WzPCQ}CpM_2ilLh#iA_pByHYxW|?IZ!V~d%iu}2q)ALO zi%7b>=)y5UItJ zwzbR`E}Q@^BC3r$a-RFhM3bl7w|v^ITcj|2hrh|M_d8)J<&X#HfWvn9@Yje6Rnik> z%zJ!(J&8)IX2FBk8&xe1fgkjC$6*K3tpGBF-=_q5xx5|SI zk7&X@5jhhXkH4>x;T|-77ZN?YE-=;2g->=r7|gz!$cf zYSwzz`}9d@K!wITucQA}EC2o`l7w zqk`Ot=8;0UuRbW>xC3prvi31yCOi+_N2Xe_=rKnTLx8%%5F7f& zS@j4-u5@nguhUlqqz2a z+k0&!ARWtU1W$8bL5fP;i%`P>A?S$rF`ZBuoPJ); z)QglZC9cO`KNGaRfx&9{D50ss=yP{NwB$xy#!N`b@}iqtIx`}01REC#PNqsC5PwS; z!4<-ynV>vShv~CNR81WT6LbB8J!zNM>v;%rxdD8?CZG>nNK)X%--H=w(^X?{8Uy+HCG8QmKu{>Rut=Vy{ z$W`79TbjZx+p_Mh$UQ z?$O9=!>wkc4*gvfRXy%D@n-B7RA#T$7eD!<1jf3u`AW#!y57rW(ucMhP5YYAMp1{O zeqkmlPF9)N{Uql5Ui`zP?^GAP|pDP$yDJ!Wc_@jqU#$2{or_@#Ove@~156S1_& zJ7nnuNk6EJ0p8ce$h0PKg+<@Mz$E|3!|A3RhK!?O23OciT_%UM9#eJt)}Pcc&3txL zXu+k*p2*+0zZ~`J=l&ahv+c<{4x{co86`@+3qj2%gF7YG{jfNu25p{r$WBp z3bSdCQ#3ifIK~b&KT`DzzpEIL9K|3Ut6zY%I4DKn0=#dqQj4r&%}q*de$3(5+zV__ z`+ZNn2>tyIDam(QV8)Q@1+^Gn8_&U72ThbR7qW>e@I-LRzbzq*JFm6cxOwUsREjB( zFw4-e>$JNJPi0EQ{xE!$e>px#`jv3HJ~2pDIS*G+_E)2@pA}B4vEb+Ud%LybQC6(U zEGAf1qi3=M(s8*+W9s-fOfH}Jl&EV?(g$_!Dt@fCB1KLDqpVbZmJx=ildkhrEbq zxG8x!)eaGMB2)XNvh^yj)xJ<&V^uL%h%^d!lx0ApTJSvKb4o(qPns25s~2nCw;>>x zqxbHfOa*U!pY40j)5oj+^!b9V3*s+jQ$yJcyv4o+FN>#g{EovMoA$MwT$Px5H~DlY znNsZLs?KDbm5M9h3?g2CkDe0%&P|DDjdogaE6>oy#v}$bv0p6$FA42#+8DxbRM*1J z=Z$r&bZwss2OipgsJ>OAAyn+kwzK`y1}Bt454BOP97kGY>J0;T zTt&m-mSd*YR>{Y4ht;9b*q@-KL#1_4ed0{O2Rm7K^UE2rej_h`ONI2c6r$=^@-$wh z=Y%{}wa~5Wt9p@{viXT^ea%=g?9VzR?;G$)2KkFfCBQhtWZXFznpCAp_QKSfPQ}wV zV~s^-LC_3LeXYdih=&XKY>v8V8bn#_Gt@V=WeY@HDm}%7=e&j-75H{yqnqXTlyK)^ z*>DxJxCmC7a?{j5%h6JPY$COg0+V;c?DqAjB(ZYBo!V2JMsVXbGi#k zIjl@o`dk{UbE?_p+ys4DZDWiTEHf18 z>QekK8c4iPufwMr@}*)`+7A7*dhcHuY`>6M zZnFHM%IS}dI#ac_DxMAr{nFw9_hadqIA@<_nPE%y0lGXBW1SWsD2-3S;k;8F8qZ@kIm`x2kOB@#I)lO>jK#;e5Vv7Zj9He6W}1yT0_tP{zs zYckDl1!#>LbFf`-%pvYXjwWq9q!NBJ;W1yNk}&gkp%MDMnM-!|-p}3unv8 z=@+q4s(-WK%{k99@R&zKqrR0HNIXe6z{UMg0=oEGvBF)0;@!b!GF^ymoP*2~5( zSdF81wDDO2P7#<>iDOa}xIbmYJSWt2jm`MLv8AiPrTsW*X^KV&6z;peu6AGGmKwQQ zk4d9p)S4g%=?)Hcp0`3KXk*L$hRShGIVbXatdU@g#TPGM03f)B|>IWCI3MQqArhdbG3eZ z60;34k3pAZj7L2QKkD!q(5LY9#(U^x0i5pN*%v#%bJn% z?bs-UBuf;C(IF?{A`NbzYFzx26pJCB=B=Xcjy^q!enAfpW$Rdwo2?RLqFE;TYYx#-xnevzi z>VT-%DY?i#I(#JLSgY35JPPY~uOi@%VlXz=-@H6@z=W2RCQaKHfjH!qsz$?lqxW}g6C?}Xs4rd@ zWb77!hA(JW7aJUEpcHK`myh>X{6m(*PWdvUskX%lr&+QZ=E53;n_8V&|0uE@6rL6~ zLe<*E%^F``ytJ2Lbd!z4AEu8M;+a+j^mplVa6`_(QS1iKsijtZKUnTmR{z0kh00!Y zz~d-&@VU+1c#@gYJzY+W)Zom!nS-D}waL4%fin`L7~FcQ&4qZ5b4AZxkCs&<#AQk4 z@ZS|t*4Md_^QrO`zj`ZA98LR_&h};KF}bvZXfcq8_~3@q1-&M5>&4_wV^k$tQhfUq zH|jQpf(qBhy3`b4*wNlDQZC!%l+3$*1x830cT)DIXaU1SBTnFU-I+80`+@k2G;1(Ao>hy$_>`C z5Oo-xLO^R|Zyz0llX!B0ITt&l{{!@y2zP?9bIMOD(bM#IZadx^Q`xM?pVWDdIkYYJ zFbYxf>I_c}N@k)&wBf&+=@S;9yK4}B_ z25y@zt45#kYbhNqM}2;L?@9h0PKux!x_RqPd$xM-(`_Q%ed{WIN#_)|^IoqFbO+RS z3Sb1-C95M#=z252uk13r7vWdrJ?`e?-FcKZO@Ettw9lm1w9vQ9=>-K|3vZz!h2laV1kM>uTmme3q?ai#_EAq?sWWdAV(5mQ7)>MaBB?&O< z7Ce7I;LjnTeFL;W>akGsNExy7VPl6_%4O{^qz0qKi z(ac`$xkrRV&BsSGO?q~jQP$U}b{0u{6m`BxxkTO}eO1t*<}CPqByUgqJ5f(6RL)bl zQJlcXCKa#vmkhPh5--6AxxLJ;NjO!C&e!CBtWL+yWiA{8u@0d^*8(v|4P@7shYH@s`^4ymq zdAVKwwUzuDnOdnFI*7OkEmJATEB?H<9=_H=d$~W_peh8oAGmqLLry-4My-^b?#--3 z#}EWw(EK9;6L2x+n)c!}ss2z546-lJ6Ag-f<$V!AHXl44oh6~JdF2!}^fEOId*kw1 zLxtDYoWX{if%?=*oc;?2rBv-!EWeXYo)_{j9v2H*kSXjlo7O_3c3%#MblZM*`Amar z_ZbCIZlm4#kvS2Q+R!=mSCD(zf6)kEm^rdP%VNPZ&et`QM9Nk&1jvij(y|G?W14mkJqlw7cjWLJ9E6{T z&mwAJaG287Z<;>0Xd#ptwM1oI?2HjKYCEGT=D#kvy%%J@FJmXT!c$>SM4?(;6o&aW%HJzQ z&5MTIxMLW)&~cw9 zqxD;>`F!mGqffwF;MAy8vOZBB(M7L5vzW*t2R2klvPta{uKKiG7*uRopW26~_~cU< zk+-BVa3yN=3sX7+Qn;lxwf6-TbE2BIsK`*5^&5O77P|^oT@3l3xb$U_ZzP;vtFN*E zO%5{3F5&J}R!=2oRUN2h@fHevJ)=&#hWhcJ7WyNDpC<4rM)VS-(Orj%M+OQ~o)FBmj#|`zwYF z4m1)CRtG>Y2Hoh#^Wo-y-tC)#PYUE~ko))ddJNaue6Ptr8nup^qN)a|np?y{qn4PA zeo|P)9PZBG!#b>gc^B+NwSJmntD0t7sZX0WpiB?!Na<8Oo`WHKf@-akCDoAXgo?_u zsj^90Bkel#H;gT&C+dCoe~3Mz#auD1V54BX;NZRVFI1BJ?==wVm5y<%SytGa0j(9p zUxzs6`3GX}5J&MQ<4H`BrR-L?`r`B0s4uj*z)QaQ2II0@8&Lkr@vGeH^W*a1Xeroh zSKKY8K+S5yP9LVt(XA?6iF6F1R)E=NxjcI%Je)Nud%#%3d*go0=ladT#(@3lMl#Ly zG|fo-ZeFQft%H3YC%e}oHn2zxa(Gr5#XM@84+tj}D9Wl|f$_jYIJJ=m?8 zGdIZs<>Cm>KJbYT`7I!a65Y>4C!Z0#s@x;1^(&|PGRqy@m;B+q6A0D6;4ichAbfNN z7~4{uB2jYH@C{jYezUA9Meg?rbuO3p+nZiB!uX0GTE%HMJNND|-=e#Z!ez;>N-`#9u9aNbNv3*(cy-!$zEN|9Fx~JG zx8>Yv>8)<`C|S2&P4Cl0s3UJM7&X0q-W1niO0KOjc6$CsU&&T#sC!Y2tGLp&#~`V$ zo)f}}+d2FaLp@-7v8OUU+PTvY15Hy>#P1rlaA+LUo(Uob(D*=XD-{;6bE}8hG_GtH zzA2gPw!~&Z0^_#oyHrT(n!&O_#UVIRkmvr^4#Qhl|w(x5b ze*?Fx@pH3bZGXRIRl39a?k;njF|2ODYEd1G0+F1Qyrx~iH>+%oXsm1; znv>$v77x3Wz^a)~oK!4^xDgm#>f#RHMg+8Q8|fh%9~b4H+W*8(N;6?Jz*hVr{#c-E zA%zQ~s*)DsU_O|kZ>a{b_(wwlOob6rO0^} z)&lIfyBvln_hbSkmW29Y|1`t-2g+a}qTD-|S=91XC7Hw_ybLrc6d4^fk@#|ajJ|;{ z1R}jb`7Ik8)t7w8vRAbB_GIZ@7j{2}wR8Ly^gB#TuCS6+J7JC1(Bn5Z@E z!Vi^3`wqJ^hDU&M{+ihB>GOd4JodyKp-v)dNEF=n69se7?9tn^xP#vvRgd>0jx5lL z7yZO$a1IBeTZzLg9c4syOb9(EL{n7s(Twdfys>yjaGE*6T^b{*^}1~^4s341rTNcc>QZreqM!i6|LI=X(GB7) z`j~G{7QN5ar^i!YpHb;>xweEl%v{57N+}ShEon9^L85t!S)PRfOOAr#^2Dd6W#Np5 z#$F6tuiSmfrKHpIoU>6*zE7L# zKkeR{oOB|YrfdP$=2YFQiv9Zl;6}ju-!b{XP^-w8~IPV(%8&e2MKQ0AF!di)h zeyk4`7MKut-t^qA5ngS7_l%E-1LeE3r`UTS2)dv9yN^0rlyW~;R+8Y z^_CgmWi3eY;MzYf)453qQ9Eaqpsp15#^~>{Leh4gE;(SS^LWrTjLiY|1926rNEiGC~(_=lq(er0H)Er)| z{No+X7x6IdrTlZ+>xsgc_(<-ZEenzQympV*aI$iF=t;E}3tljr?O5kv_6}cU2kZzooWK&D%L|STp%_`I^i=Q_CbX2JQxRKBL?bw^t5T zQ_XCA4M;y1Q=vw2>}(LO+urYi4yi?Xxb-m?Z_(satCJd7n?Dusvf<<2R6e;|xPc@u zHak&uhh|{{WEP`(>9VaUD~%HA`NO~hNW!m?aMe$Xa1_U>)48m-Vcb~NYzZy{u zpUfM;=u+N4`c@Hbx{EsV6F4D&@>)0u)!GhJ?={-UVTFR2$ZSjJL-1W^s;>?7u#Fwo z8by<{Ta58uX1v05tI$Y=^G>z|NT_6S_|IxFlqc-a^`5P%!pWzuPNsENc?YE`T{i2} z@3LjM2o$UJxzK=7NpQ{9U9jP8XUgn$tLixpHn4uuf4S7=nNK*0>RQP4*}Yuoc^6jK zUgx_IH9+PKH_$_4H})Z@3SqLc?oVBhFeAg=NLJ0WIrN+KA!o~&4Xq)|jNWGE(c3cp z1=|*pV+&N7Ts4@Hd`E2}k|po6xgIlMBYJMku;2>YuQjcGBkl3Oe-QQz6vcefSbIP^ zadn?iv&f8Mvb=gp7|B5&pHc+0zy*R`WTSRe>F6){=#ab&H^-j8mr_Uy)fo-5$#3m| z=?;+Sv;5|RgOZ&HjXDjYcFwM`QXG_MpP3Xn#)Mt$;+-wyZk5_AGNs81t3% z;oL~}+N>>a$}B`kZ|>z3nh1?7YcpEhO2+FitfLDVluq<4O1DD8R+^I$K{X&rBjV=hb4+Dcjl!Px&Z@TH=i=k1UV9Y?yo3Lv=pF#!ufR3k&Ww=k_+m&7dWJijaE=Aa?~fY{YtxXSkC`$tdmZM zhx(?UNuUKtWa(MoWedF~ukK_^2piM;2b2~{{V-_-Yw_PooPaZYplDzipQwJtIG{$T zknjPGxkUlFEo!h)cWm*kC3pyR${qTN4P8+ z!~HWa-h{2*t@PNYcqA=M*IbwR%ASj}msZR}JK97Z1&YBrQQPt4%@H zy4ay~TwX8!M)D^GoVM>QG5FC-E%vO-Pt~jDNGD4v=(k#4TP}}0R&Jj$>TlQLjWdB7 zPa?a1$Ao`^_BMc>&Q0f8scVAO8zrF1n-X8L(`W28vv{?^s!|YAXZBfq+RZ&~qt5+a zEZT2h_v&b^slX44R0>8R=cFzf#$Qzjjh9F6Ol#!kqw$l5;heuSwLO0}iSd$eC&KR> zk*mn*E0g@;ixDr~tK$uo^Jf&h`3n#ht8s&dMybo8YlZeQ$gF24?=nxYIow>~&@jA)eGeU|s*yHf3KwGMxIdmaaE9Iw~5Y;F?b zd=RqQVOws%nA&V-khj$hJlp~K>%EXMM|udjeIFD&^&H!B@k#dT z5R@};#l45ag<+BanEJkp*>Z$m!Kj@pg2#F5^mSeX+4XX|&;$;fGNc|;nkiJxhMLSm zFmTWwOR-$1CvmA(y#`MAlr>!Z!g1tvyxrG`(+|il4;r{f?bWIJ74jF!de{VeBdM|) zKEUbUNPDTEXVGsw~ND2B_cWs%ZUSt=25!S%p-QOV6KDyL*zgqdhk%nl0t( z@-9u?BZg#%F#j0iJ$ zr}?L2WrN5h5N&+Fbg=63iM-5wrmkM4(F42OY7G@Dwu^)lp}on^Yd)@#&NCiwmkhjV zGx_-}IJ8vFN~KIg5aZ@#_I&w6E=L-pL;*E&Gji#Og7#xqJTd%t#LvIKs!vxC*>}|4 zV$mPBUu{5GVUEGMoe}|O)X#9hBD0;>>*iG;3yGr8nNWvgX1I%gf z5#Uadiv_X)sk-Zj8`(_+$A(4;2RIO0iSko7DuGY-3w7ve-}#3pV*uSBv%qr;Yx&QB+(S$LKhBB$k05ORny-pK~ z{}j+u+D!Xwf}Z`DuJbyNs#|>npOYbIXW`6fo|?;=j7NPHt#KH6v%JgJ!K4#1@gsA? zQ$=aO*J&C$8Ur7OI(YA9Pb~3!4s8b)RBY3Q($e*-%(#n^0f67o>x|bz?&|+m&;?Zz z;!ckXB%kM+FIGqx8zEYP(s6IwN$BKd&-P+)G*8N6GN3j9%2%(^R&D08WRIQk?tUn! zwR~r4RXcecHc7QRUD@a|;TlF8A8ZZdqEj=E`C>F#1s1uVxM=yxVi$Hyzd3{C?p``s zQ;tr;xwPcSjTz}`j+DjCj+H)+w6{ig6;^=`)X#DI*|J0Ez9&A*L2r%s0{(wv19SUJ zXI7Jvc08uKVMQ3@A@m!J!qOr(KCUTfl8EeJ5fgjgxtnx1Z&%tq6NuU{&K zi=iL ztZffwJd=dL@xBA*M{iFhad%&gLFHrs;>HMF$9?jt7v~qbX2)kKA?YqlR?Xry|9SvM zGsmP3-)xky6|*~_5Fty(FY%k^`3sgYVyycQ#NQQp&qlN#{Y*Uh1>+6XFXwQdL5k5K55P2S)>pf|n;UYO9!oW32!hatF7y9VIm zCDc($Zcm{l}N6Rle$(Pxd;ss>;{ zRwjmaO90lT8^QJ@Eh{A@U7Kj;v&hy6du zb-Jy;R8Y;-=!j`&QBUKsEwRA18G^b#%R?^{&Uj4AKIj%9wx7lIjaYy9bGGji)x>y` zcsr&-bo*Gi3xK1eQAmv{u7%$?C0;<%psU$!W&SuB?YDz}@fsO+w9Bt6B6I|Ci@0LD$R zJZ|9i{-=ax_}z&VjhIUH&?f~|=aU(fV$6k>-y>WUCppuG5}C{d(gkfBF`cJ)UYPw< zb=7khYt^G-vErFlFqg(fOelUdT6&f_;5mw!4viXp()34t{?co#y=^c6o{?BCNdO*< zs2&5-N$_QkiA(x8Hj;%5JNu7(xNYCxV{!MtT3aQje%pR^CmD>G z@`_#Fjln*i@1mW~#nUP}rkg5o4~T->pixz$WWUzDs2Dq;C&cj`Q^3hF-oRrKJMZ-R z8DI*Kc7~q^0eN7=WL5DPZ|`AO_!Qf;MQ4k2fT{lug6<9NW#d}6uS84Uk7M#J1_i2- zVcp5A>XlN{frvn}r}`Lx?-&Rfo9kcol^(D(0D)Qgw0?@uhahg)r$6p4DbEj;sEt2| za|_P{vx{vBa%wCf$qgS#ZdIdSr-h;7$ST-V<~WLcKB)w1(N`ZL;dkOs{Po_AphoOX(-mdj?n(lMiE<4*#8dO3xm``bFA3_9QoZ> zo5Mhvqaiqw#3BgDAu`(+BtrN(*CIG4jn=AS{&7E@$!uLxcjZGnwoteoSGD(Ocq_1` zX|f6|t8oMLVZSyVEl{CweZn56Q%EE{-MqBBR-oe4 z{rQH3H+8x68Hht>x@LdwIL%yM_F)b$w}&C_64%;>x_OE@i#Qs8X|tgCxfgnFhqA8& zJI+bapj_C}_iW^j-PMb9S;wgQ!3 zSgPuE-~NVgXnI&x_tk_Rdm0+6I1DE?nZ-o1CcE5OOc=fhwe<5FtCvI%NGTGIuY$fP z6Tt_)rXv(E5DH0ms3{<82$D3#W{MvvwJ*J69BpTzdu#V4pD#*(NtTL|@)5z72nuh6 zDlyW+h{7yLUI4QWy;fy%Dy~9^Eix@V4OPgO+7sm^V9}g!TbnN)5BZIIj))Anp>6H$ z{i|g1q4967`5rt24>()D=iTOoO?W6a+h}Szlvr09R?G!#!w{-$NjPqNK1u5b*?z40 z2{N9v#OZ=AjR&dVaC z<7=IwTOr92@uy?WH)!`!(9syUKr9Vfip%kS^X#eKXyP$+T8ILwM5t83j-R;?ETnka z5#A7TKx^H)|jebBU?NN z?^O&5V~g~q6gF$ZRak!}?~(CzV$*|gq28#-gRPUJlfdb#QHq>}@&5YZX?^l1>)-g* z3r%+O2rEd6bGV8LGPbv)I(hntwDgeZ`4FJ&+HW6LHku1jOI9-FF_qOkC=$JFjlg=J zsXq+tBSpuwOHe*|O?vJSf}&`qv@P_hT;|sfb?uqL)>H02aCZHAhR~3%MQNnCgcm63 zd9IT$lnX*Ek?->>DJ(!0gKl>N{_kh_mmx^U`WINaO`jH$_qdA_0vgG5ISS1j$(86`*l zBO=E0^LG4?7XS>fK~PZ0;arpm#+@_QHUcGE)4^z;rQwdXHk5)4;#C35r^%}tQWGC?e>$qLU@ zfsXJ-4F~8QXg}8NWqo$}m~vLa7g@U0Y!l!zX_E*kh|KOl&s(=+*R^QCJy$US##_v` zjv`)gL^CfQ)JmQLYrq`vaRVQHsjI91-)5K@h(o!!2kxY8Cpd9fp$p$ zokH#b+Zk}vF)Kq7=LTM^iWDG2=y~2enL)=WoJ~6JkBmexWjvBpNdZ=(z~rHR*=j4o z`~sh2XUiTNYgyy7umiRiPQ8D{4U6#O>s$e(pQEjttw8Nb@1COhE=d}%Dc^9@iaIN@ zbK9*`zfH0a6X!@0D89k9r9$4o7L>V|PGF^V&`M5SQvuV<{XBttBX&`8L(k%fy9L#s zyfXqYv+u5c{Q(3{VLze)?#l9q_s!pXXBl;`#9|S6U-oXCSb&B?3RC{V_sDyrMujmo z5~hpaki1uyNCB^yA1Eo&E(%3a-joSXCP?pHcX;Vvbn{s3@fY&^a$_JE;9fk2+%{y; zYj9vmsdeWsMTecTsHE$93G{>1)LsBt*f5j~X3vytZzqnXl(3;?m0*vzciaetkpejT z?q0VE_bHek)_sti6LdMaj|lU^M)kza)w%^w>yBleIF;iUnX4%|4q|Ev@(HOr-}B%M zl7*P=1?nEsx+|c5{^b29y79Bi)(2Rozk4>F=X@9+ zuK^kiR@?P^63n)Qe4y^buU49G-(VzU4iM>i>e3$D7N}ouJ-qQ1xVel@96QMZs@WE3 z4cD*Ufa-Nc2@mQ$U|D^Z*!!KDi#)2dC=qPTn7Wt8TQhHwKkSA@LNw1PD}K{9i4^O)+Y~M-hZ$4`L%?@is&c1 zY>{iBk?$3rxt|^;9vYvYVCS9@GW1rkp2&P9zz3Xr6po`Z#F?@ zJJ5NM^-#5}&O#xZ-Rv!lJ`TyBMXtz!2OX&$z(5|v?f9>O(tIS?u?AEDi` zcOPcC9XDsw9*WmV5qa$;?xFixc7F=mq(|K2I{irVE#$Rjw?Py%-=zFJN>rEp=IIX# zTacx{s)E<+sY}K!;|~=q7RN`_KERMNfJS6G)#gQUSj0=n2tLnpMV+T^pZMN4W~-7l zvdj0XRuAe5{V#Fymq@w9hFC8D{5v@Gz4~2^ou$IH$5Fa8*nSap?hC?{jTM-08eLRL`Zf+e@~sf-MhNl5)$nbh zr7Q0yu>XZh6_8)f->N#L6}yQ6aQAd6l)pPWy(P%T!zZc(y03-Uh#XknHF2EIGcSAy zEXHd`phLpb*=GblXBSqJ;0=YeHD)J==!#}TD}bLG?e24Y`uvNnfLqB6lkAz<*?@~? zUHoOBB7|KZaeJUx7pO_Qk_z!GtPgzNv>`W#`=07C(1H>d^;7(nVqe2_@2uwf3T1}Z zsPDI%*J`yM5dUM7zLrZf0T{Z!*9RMg6_))>*?j#SxZ-hF;#jhO6El%b%=3WMa1kh- zNbQo9Klw|t_rh6P^W|_8J_FLjtCcHmlGYF128qT#%G<)LY(7P5DNnp)K}fCvlv}#u zz)5zm?x0DN7RJAV_dr*y#@z|}F}AJ4Pkwi?e5>rkCXA|It1I~30eCidB|NOI7F&my zM=?Uo55wT$ElPXOtv|VOwm(oLr4J@vAqFs3zsh9S%dGM|Z*El6`F4E_G0_klWSTkAb@Wu~gXsQuZZ%_mXsdxQno zZEvj`4?t#KAK@ebJjJj-R@h<8YlQzKO>i3Vc%oSCNu1ca z+yBaYoex`KlwA1e{=7ibWkjKTZgf-l-5O({^LDRJYxnJ%1*`G$t6q}Y2K><2W^r<< zE8I<*u*qA#&c3D=Fyo3l=}yMGU$zVWYC>V`FS#X(3Be&3m0>2meObRECN0%l9+!+o z!&)b;)(0ez<{giA<^vCkY2KX~p2=Dv4s0|_?!+GEKU{1GHZxx!)YT6S5t-C;4<4`PQ$1;Xb}=$q0_-O*JVTvfPp9WSU`*)zk{vSqoTa2j7oC z8UygdvI5j|F?V06YzSGTwyl{9j7BMyCwsSqtbI-u+D%S0Gdn-(R`*y!qLio>3ZY4A zuCDQkMuskB;%nzu)$^5=KwwtTV#Aq#(J`c$pl^AjP5JepfhTx;(3w8J8PqH*x-fJD z!thoj!N57qz-fUg4_wl)4fkr8w+Q|4LDRq3ly6h#sZ?4c8<-0{Bt9(PLTsZ1%5{@C zHHg1JsN?tJ?+_WYuzFh(YnIMU|O zqM|cJ%*b_Z!mP?D+mm0YxVg>o`vMhvEiJ_cJA7sU={9knC9UTvIEHO!qG%W-5KZ%d zUAWyEhx24cOy=~4GS{(fDCdPu+?nmXHJ5hiYr}J(v^QeeR|f9@Z87gtvVViwD+VUU zB_F{gyL6d`- z53OIQP41whv=kLT^DEr)#>8|cp2UlRV?Q=w=kNI-z(cwSmztg_GjsEJ0wj|rs~xJp zF)yFFVfhxb@GV9QdwDwpyb*Z)EICDIUq7Nx$JI4Lv*=R>3kXymUT!^nxA$UmsW?f4 z0kL8|nw1D#Njq-Sb29j|kU*uYC)?-7Ic`4T*eCIW@9f_&D|K!U#Ke#pgSrt{fCcjV z^@sH3)c}o}4D5VIpfS?de91O3$?c>!JEQObs>6}A$Wa`-&1rtG#Y8XeElB?c2d*b% zL*Ih^A=dg>ReZew<{=HlbU;Jg*F@wYglMYx*|TTuF%-da>w5gA;|E}j^${DVg zxw(ww9q)4wix-=#;0C%g%!*6-))|lK0<_^;`MT;j{;)wo>Z7suz_0=%B-^|%NcUKqdC@DxBqj|45HGXXpem+{p`>5zpv zRQ*R({oxdx*1XuBQwGp&Bp2O8e_VBRGdHkr z;$WpCM{lq_?*?LaG^1Abg_c&{WZtPv1ixThd^|V6GJWi;bzZ6EqIC5B4$mO3s%Qh|l(7a7${ z(}uc4zx0v83rGF{;(I9UG;b!Au@|5L$Qq0jeMR2YwQ6m&)+p#@Ir7OPaq%f^muPn8 z_B8JkvzCZSk0v3*>Dx1jWo2b~SVL+cf)(J7!>d!C8;e<4S-CRdP;(>M2wG5sLAgd> zU!S8k>%t>2f01V;yLxUI?9NDhvB(;nNFmEIjsp~p3w`f488<&4~z%i+9PcA`PP5EmDHXJ*q2qQUvKRO_9Zyk>uy8~mM=y_ z-8e6LEE4Zp%x~2*=1~=%J^=_teo;3W1SzBQ!(|x%bE_}cqe)~=wvjpM%9vVKjPR3q62JcXEgcj>Nx1>FEBy{xZf0&KGMymP& z3$Pmw1iuF7?MKf2lMSkLRCh)9_gqx(W96P88=et=Ol2Tt0L;zR08_p{zV`2Zmn_5w z=4!_BYh)K!<#kC^$dYzEw#)wBJyOYxZ`kq|5Z_Qbc9EwU@34gW&;yI))E`eS9N+sI za_H)gKA+&Wll}esUr(&I{;jR}=mB^lGP!7tR=4g=Wm5N9^<9YZ?GL0Wgl}Uy@7)43 z2;R+z9o*7%_c^?E1~n!6Jxu>w_z|0D{^IOaA(9UG^R0z8NiPiEd`51kYo9W*6`~Iw zGyeYZ@25sKQhF-Jbv=P{nuMhIdmTRdC3(%eqhgJ0C+aV;uTBGA?v>o4p!}xYx5JpY zALPGIPyfD~_-UJGxrJZr$+lz}WR<`L^BjS#?LWIBw#bnl3cY`_CG&}yAPEE?$TNNVKeO^Ib<_Y2+UAO{Pzrx zt}tmfYy3PiUOicPMes|GBunwJmoVoqdo$_q1#uj6WGch@>f~%7ioB%+KOYsWu6>*vuCXaKp`bv$#LZ0&lUfYA1#-1Fn@t*C^uIfln z%26aF{q)QHlGpHlW$9TVOGjYK-B_zZ_HkI<<0T~z*D(b^#A>iytcIgq*Ohts#4lz= zlnsAK~ zIE(O8@mL#gWe`r2XisZvpt-2eXXyIoRys^qt5O{;tCQtR?)hvS)v(RwXtZdUwyAXF zUSx!vdTDQ1r3$C9_oq=aef;>*+#K+@0nb6cL>$w)>08te{`g3Lrm5SagJLcu5&a%9 z@j%3Nx&8w+DL%Jd2B#18h?K0?B2s zO_F8fibvQHY7DX~1JL_QcQmV~)02|!0fj-O|3i?u{WbIj;=g=(U8}Y#upVzCvd7aD zv{~T|XwJf4n)4LU97{lRFw%na z-l6XYwHB1AbZ8s##N$(D$t#PK$AgWIoWcPs=QRFs&dC1}<3>USN?7UKphgdOLa0s? z-2T)a%7AKZWlhAin_BkPg?jC^4fF2(B*?Yu?k2-$@$WgAqF1&EF}We`Pa7H$7Td3E zxj6K>@?tHTu9o;qmkHKyMfvrVv4>+dSn#0h&Y-<@YqZgjyo`B#5>-9wkU+TxOs>c< z17>ih&xm#QWA~C!L>dV(31GgME6o>s^mh@v6#NYN?onGsR6R35MURTH0&#R;-Y2wu z6`|*@i6P5@>@3~6CrFvY!h@g0Z#m282>5udVbd4UQ{J20gSX;>W?~!C_^mY)Qfplk zpaLSY3=`N-F%aJ`scaDf?%0kwmd#1(wdPEAC zjEp&{&?l4>>np{w)#fMN*UD|PH~g9F(swCR*e9)HkRk6RN8SC@4emj=&Pi{?D~zP8 zDt^Jq_c=ZXhlYoKTr&K`!DrrmBVGe!wZ3&ZbdXg(%d*>=T~Gc4;s+@K8caXi#7QN; zf-~NDZMsP?=aI4+0xKOJ9)Dny^kv?WSRCk+B5+xehsyhM*uRhw&|KX<`twqY@6YW{Kp9p>2LL8VZj5 z6vya+Vhs%!wgkr2A-iaP>!FU1*|NKJxrUQ~(K8ma94gA3aBAT)KbjDj`uP35@1r^3 z;-1*`aWDyI9ZtS8e@qoiIzt=>OB=LdsI46Y0}Z>PJr3rqe-ISwy_l2P&r#2PFj{FH zdAg7B9R2L^zGQH>vqY;h%%KQefp{}k-t2s>$}N3ET6~qBU>a_ZvZhE;DSxi8*Kycz0Us-3g6={mIDz4b{OMZdo~ z92d`(Bo9dI{7j?t3Q~UoBcbI1UwmS-ZoOJXx2X3nEG*u2MYLKxvnD6cqF{s4H>N?m z#08=zlONW|ND>Oz0HuPNW>iV`M1&*^Ut~pqfjrwFo?| z_MgCY_9HNBhZ;vR=owe8aWf)XHM7p4Z+IsS33pl>mf6vjo91Yt8y-n{9~-NwTX8^d zlKe5VVP=7@$f;BV46`1BqiO?^vQY{x#PHn)^L5;6D~V%Io6)90$}SQK?zpuz+EBHh zb+@{bp%7$ZPF`N(?%q;+1JYI>^&ui6zJIK?>LMR8eXuFPKt{GO;5{L!tUCPE;LQhz z^P_}mCcgd?2lsJzp*e_jjGfbOJP{O7`<=gbC#-u8ORB39+d9YsaTbA6SFep z9gM+`owarj3SN%E`aK7L{|%9vcW z4zeLcUH98HB~5X(dKlqHa`kS;p&voasMgGKNyBhkxc6pUrZjFVNZVk|b?I9HX`ZCT zC!btyoY`)$xm`z$CUx!#pNFD%ek5-u9Su3WnXJ;_ZrCxe*-RVHSsF}$WxR+ztKZvG zOca8o)jeG0hon`1)EKY)n28~YnoHtuuQ+UB>M+~;(pK&=ejwUWL|s=L`M~bZTy)*!gOzZdlcbYlERTiCi#8zmF0`NAJ~*hTr~q!bu2@*mzJLWh{>Zyr zHPwe%ylW-eWGEXcDz@;v->^FINb%<4o$c$&l@gVe*VTsQ)W2gxNQ65v;fSe_>onZg>KT3z361}XkkyKjtLwURH$E1lN3LLamyY*y4 zobOWe%K>0VHh+gtWp`r}Mm$bdR;7GNMAmuPp?7Jwt4PfxJ?;Xz`w=;U?*G}zp}4%& zw%z>TQCR>o;;wFuRq+RJFX-v^l9BE1ejxEp@BL7Y*8FQPjuDAFd#petpE-`GbzP5Z z>$m8D(0gq>nRc4h*?f+2U0zqdy_@09neJ2nB3SgX@=k3&@m*0)3%B|o#d5+g4x)}f z)SdPbX%=u|DxQ~o_ULd;-1>I?Xd@(jVrnjh6^_DP65n&rm>`6l6V9Pg`(tw}+>R&3 z0xt&HYbF`dbN%THo4$uN%MI7+f{as#9Z&cW?a!&wVwMbtF)IS^;|n0(DTjoNE32F`%oayQ{1mH?3uR>XvrGr&3MXY#K15`6=Z!am8ya zQAxHWqk@KULfSK=C$g}rUpgqU0hr_h$J{kUd?*g=5a@g z|GN^9`wIL-)`O`g@5F3pt4CzbOn4?1&Q697Qy$8IkxxExF93g+#`^+a?Nv&GK}EWy zBua;x6?jkyk>W>VAM{%?t4{{AzW2eFD47E*&DUip;(I`qMGt#4P~EZGi}f~BOGnwy zcg=Yri_Z!(fcM_x;!+a3J^gCzWsukT=$GfVbqnI4aCFAfc$`{Y-9wu?!W)&J6D(10 zKsfT+{C&+Ln?6=hA<8F$`#)eB>kQ%j-*$S&H%3n)L;)O++qxeSS9m99c(iuYZ<$ag z0qjtb_E`Gq(||hBRW^~CENL7>lUg&b%LDH2eLq%yevoaAcfs)250n8tl#RlLVKTz1 zInyN^-yXkF9bS{xH|(p7ypBLtJ2&i*Q~2oY58nZK7sYGBw2r3Z-zlc-)g3~E%}Vw* zUl_y5EI!SK<{Lfy`M_IQz-G!kfC7=K)w2OBaUr!YvO!Ojz+0c31cg?0G)lKYk2_sK zAM-*9g3vDeXHM%k-V%bn<&7i&9P?2o6Ozv6V3)_YKvJDa!$Tg`-&|0JD(LBNfrt9~ z>Mb#lHWrTKk|dy^&_rTe^e!A0fwt~O;O*6kY|^FX-;d!deeCs(!f`mGUOig|L~a0_S*tR~oNPL}$`&@VJ8x#q%1f)b*a>{;%gqr4bE0KFqpE&KS}lSWH;02+}@& z7Fz_}+DH2~4ASl&kr_8>Q^80h?mD+B``T)i7!Goc%)w4NXHVah(iF;IEx2+nl{`$t z0hegI*6PTJdsTbC7GCps0eagH!Y}?=fokBtCkty&9(}0!_8Al@O+Ga2i43TEG5kAFkU#AC3GLdA^#6Fy& zXSWD*#Z8xtfIhs#!jj(eP^BMVW#OJN?te#Q0NgP3RKv5S;k-sIIx+|P=&8oo+nHFc zmy#J*fCJ{{70n-1`u>b(s=~ayO`?H@J25Z_RJhW*O%g<1&|$U&!)vB_V2JaYR}-UG zed7*yt?T9!-tap^+Hr2S0$@Wlbpp6o+vP@emy(j(^y7U?ZTvYTNqv-V#dK+HME}W` zN;{R>_Q60Fi`Kd$qRA;=g9_%Ip*pnrl%GgQ2g&5?3JzlID%Nq<~q5%(1p zTc<41M;rGjW_wmsAjT+Bu-G6Jq+ytR_JLpB|ag1{|jwE8DwWloUPDOIaKne@W^lVZhp zbLAYzBg55Ga2H}MvXnkdm)d@iV-8fl(ivT{C<=lb$aPC>Ue|Dt!|Qb2oMM@T*MOYA z3tHRb#_v6uoqJ_A@eSR+Ei#xpDK)(BJk|!y8kg){(xcBOPIK2PVP8QmzX_>7cUzz0 zi7b?NXgEC%q22G}TFwcL{A8k^iSS#U6c8vfp-b?g^Ey#-T;&>0{5p`+R-ySln+Jq% z-9Vs;hI568J#58h-mi2yQ*s-{AS=V858vSD=&N<7OQaovODxi+q?lFi7zaS6&q@^y zlXmksFV@{$kn;sIn%-#FBb>Zfsa1oZjWqXTkN2Z>dYF%CJ;P|g!ADH4A`ofy2}O{z zw0kCs!{OcGI==bK?IxJ(9-RgvT9UBAZ7xkWn8^b+wqEB*;jQkFR68?T|BPER5MR~w zyLYGtu!Qn}ibYWDqLKlnL26g-xOhJ9+;70^?0+hBGrdnUu~P*lUlR?VELYLw`ZlcY7l>{TIAy_PJcxz&_&4L&@*uo)w-FmZ z8f{@Z_wdDbn>su@y20?5sg=?2fZauFjjnkBGS9Kb_;^5t(6$UN!}gxbUY0zR?jt5Y z&W?Vf_-C(cEHlj95SWh3PcB&*gtbBJ8VcA$zPGO>OlwXO)z4R*B&7L19ZPkLCU8Sz zHb*OlFQ){GcNPfr?3o-~7X^#7^lVLu*nP}xD2j@d z0MBdfcM9YA<13>jH7=x@P0{5>V#y5M8)W&N?z-m4`IRJf(oG>Aut}BJ z(K8S9QA-zcmP=G;0Ma{3Oh<^^_#rYNG@hG0@50Ku+fbRLaw(NYP63ev`C7% zDwFAk3VbEqC)oL%1cqd-vVM51M>Bzq_bq0YQ;6XFAY)My$l_j0uJp+hX!HPa^~Pk% zs`o*obzV{o@#OMWIKP2w+Yztr9x0zkV=i)C9yH{*3Uc#(vKiNA-p4mTr^5QfgLV=^ z996mcL?^k*Xuo-jX&Pz5&hHUSZw0g66ZgTGRlrN|7KVb;l6jRu#=_qn8GmO%OuNKe*X2l?iC)Zl*4=4;M=z40wzHW&QuYTkSTLiwTv^U&`hGvj=gmm zPADt-q)pGXVW`ANd+K8Pz4`IRy=;&Rlzz<&C;ZSUT(J2jLOph)sb~CUsu9X58+PQi zleT(i`xfu#DoQdkw)f(`{mx{C91Ef%U1DBl#Y^iT?ejj$vCAYamEqL5waWv$&HmjY zC(?H_0|7zlt*wvX)&owy#|GW$B9nm^&e|?wwzprG%wFfTnfMHXf5}Vt_8s9k6UpLu z42CFCQD};8X05K1>M(3Pw-DLcEgjQT?Xj3|#<0CALa-k(?-qQO_+oPnft4+jwkKKuO238LdhP4Jmx<5WRyL@y%p(E*-rB6T) z?p_y;g-P(RYb!^qi0eNfxlUtIqA>yiYd;$B-PCzo|AWf6;;SS=G$Ax@L zld^Bm`zy^LD!FY#L99^pwYM7#(adGooXl2A7nSK=c7{U5E1n+TaR18DA##z*6izcp zfl}@K^if(-+gHSOE&e@X@8H-_j8Dcz1T&X~!>q%n=0b@XtHU`!oCm%tn(LViPCZJ8 zc?a{~s^?d5+1FGNKU`LzK3IMRp`Sz;ux8UEhZQjz;4Kf>XOrSLL5zEQpy4U*)u9WM zTS~Y22Al`u)6&@TQwNnQ?{#!yPBUTdTt^3VPDN=yX;CiTN>q8XvG6N-9R40OD?^1` z^xP>nx6tj9h^7xEjnuX@0uD)?7n_g+s1|-x|Dd5%xQ@*M=zE@39Q)1!3tV0t;Adna zo}M%v9eA~z)ySk6hppPyX@G9(ue&5iiaO^Tcn{`6t>RC_e%8s#|0EAzg-EJX9Nm^9ZoK&MyfyW4DNH_Q>12QJIaLZH1qhSq zdD{z>y|eBnk?4@WTQ>E++tvMU{%B}RWI%v;dL0Hj00n_vQV)YfMRy1y-IkPHUr%e9 zE7RMV?|m^Y1=1PC)I6c6V98p#)uVnqgQ$GNmf?M?v(!wE3m3s%!ojZB!G@Eo56${s z_wO(HUkjN}Au<3>?S*w)kETPs#}85+<+L&Og_Ics@K+Z5yi*4vaK)z2OozsR)&5YZL@^mFVc|1uV-2AM1$`N_J{!FF<<;1gw9s*@d zM)}HiFAo^zsrYm%3UOq0COuLcOCQs77O)>SkodZ5W81IQQ}iG};|RynIl=N0w;yx8 z#O*6kR5g|TLS$4_7ELw_?QtFb-f<T&CxhY_rQB&x?s7Mjm%N!O@A@EeO(6|5*-02dk2q64(cS7L z1=1$MUO6F`{^IBHvNQ?rx+9O31?)ytJh8{?82a zzjD5$-B<{k`cKQOp^=efXPew{ly%*V_O^g2EL>+Q^49-C;;4)(Ng_Ji&YftA)}p9795SQJn8G^i}rrIqQ@Zy7B0Zs zI=Z2BM<5o*PTWq*9^pwfVV`GoM$CKXSNzngfk=*)S(vdM8~b?BOusrn%Sj*3 z5siM>8VBZy6^0VYEk(t|FivwGtKk5RIi|d|=)2(jr$TrDCnv?2b?Ll#dlx0r9B8uV zjGA(51swBF&%3`y^IbPXqK=X@%d$9+XO;L?+bqo6VI@eLA6!dW6M$j3$u9`X^zu0R znS3FXyfJ*(kLr#Va+>B{jsIpLoiQ1)e~P}wEpHEfsMXC)dXbHQeBKC^1h_^H%k1uN z@g`WQQhejlo*JkMTyO7gh)L$MpRP>sCRAtWxb8kFCl-)2i|3OPX%F_}5Y3Iul}d&7 zWu&bpNVfkdbGZ4pU;lTmgTYVIo(O>dWuMrLufBee$%Yp^&f3T(K+H_K#r^f#%}wr~ zywzXcYM@l;c<5BDVLmWn1$gZ?cY{tOtq{9@6@qRA@OKdl=8OwsNrC`x2_FwF#Pzm@M#4E zBkrJ;f~Ztcp%&dTck1*$CQ%zSS6`dM@JGkQ6thPaV=BLVJ>t@T9HTLw#%eR3_P`=_ z)bJJPIlI`KsX8#iNI^mt^9vZ&!=x6c@-r~81$&eTbI;eP9 z5`of!TOPnw$#3SxKBt_yD(`MY_?<50&e<}uuec)!N|dXJcJ7Wlc)Q+IknqfYrCgyq z!67^3letjs^zKl1y?_yi+YLvYY`~O)p4g7P(W2p-^+7v&ShRI_Yk`6__t|9&zSq!M zTq*qgR^u|Os3PG$_}FeJ<%rj8?2U!S@~?>;9` z4);2|TT?#%9LFbO<*f6QsFw2PfbzlGD9@6SV``dNuq0l7(YjtqSEThTaU#9PcCXCA zy>m}SOO3NDKWSqJ==p7)NqKv|HfSZB56<7a_e}P={Jxc(I#T{sJ@_za?Ps6;e|wG@ zfjG_yUbPsycp#Lx%nr9q5h!{1%_lw(j0*{*#-G33O2ZiYB#nv2LL%XGE|my+^A`{d zQu{J)U@0LE)&X+~X|spU#1iXyZTA|4zIzv2eU8Z#ed1GIL2&TJ@Vxzf%sh~rduL(B zW?^sK3LLxrD5}v-rwHeBRN~IS92|ZsFCv&bzCTpAqzNjKDK}r2$Q!ThRJ3?1QoVpF zSm749(4SnG1FQ(XB z3)t*aMDjpm1I~rF6yS+4OI+;K?jgG<1qWzcaN`d)L>R!em>7UFVnMOCZIMoUZz-)` z4(EFR{95<$SPRWNAoPs@jBc=mppK`3cMX)+^;tJ^OPCT{sXDS*h@76D2XHcCvSlNM zEfnOuo?wry>!8xbx3aMFQzj+F1-$~iPSL}*@k7t{R|e-(1&pEsv0cQtPoK#|IDmNi zJFbzXguP>Bxk2+}i~3#PBt}fg_={z?H%Y`tRKsbv)g9nuuDm>%kN0}ML{Zt3sG^&D zK?Tv|b4+DV>tZ7#LH%mbFtgZa&<2a!e|v0yX4@xl%FCS|FfwY0iL}RNvspUo?4LVs z*@wxxu&s)no`f-}^>8#61_bIv_>upm;kbZ?hrNFvLqgU45ei~Y-1u(0=2kN49j9Ta zUD6ABP#*0_Mj#wf6ns8ljLrVr77c zar24d&rwji_*7erOdCKBH*XR@!@H4S^kB38qOkG>9GUn2I^<2_ui?z?(H6M*M%l!u-5sjD&CD$X|+ut z4m|~o0kzOGgn?^4!U&_Vq+qKGZZH!`W@Pznm5HECF~zb5lE7~#Ff%$DHXSxh{Et>X zLO@LLi&mbFi&zm*WpV6HZMO)f{ZY~v0^V<0JkJSt2F4He69L+Q^?7%JR&hD4XzUwt zS+QDWE^Y3o06;M0d=18Nx)0pyk%D<;yy=31A78rE%n^{*z;dsktd!xc+{xqCA)E1i z<3uUEW#>{WfQoRLpx3SNc+S9j3Dn=r_m_$F@UOcH<@If)W%vht{uLH>9-)GgJ+$;y z5q&)1&=GX>+tPoQ@Xtoaw6s#EDtA>UL*33r6z&K>SvKoV3O~5NiAH*hIeK->&3|8_ zrvews;ANZFZ6&QNuDJ=hZ)Ffte^+oD?ASy(H+Yf4L*}%^ekKu|N}isQs{%$HIQ~LPyE1j=U~?P*7pK zV?7~gzy2pXK;w@E*uKhlZ|5~rY>>#I!cxNgm!fSV(ybS=UQG#ch zHEN(!p?Wio-prJ-O6wO7vGbkrjn@iJK%kV1Pa9>18aC%^9h-6c@`d`<5h*25tbN2e zd!ZpRY$e_vHsRiZWWVX$Lz}(dtc~BK2RC|O0TcIye{2e71K1kuT8gnk>|Qw~ADcrW zTQ3uA>G9|0Pbt*Ydix4Z^gUj;GEgY@#l<4P{YF)G(`eIHx6{#lnRWpC)ND+pP28Hl zezdasz1(Tow$Pv^`Z5|o59j9>66e*nieQ>nyTnXwRNd&G_-@>!*pO2eXID}2Pz$<< zmlN7Is?|`A9k;EKI)LrEK84M7@~0TtTeWnHsk5<3nRj|ARsNK9BKd4NDbI~urlsiV zJ29xW&0dBK>SlIQ>-3EmfGU#E%3EiV^}NFxXP*1>rxz*QKu-Q1CA;RstDj+TItKkJ z+X~|5x?B5(&Q;CnGn(nio~@H_Pm;V>*$sizYwoi`Ti2A~BY+NYpD}paY^ZngUbpYb zHIURMkIVJKEDrz4p-o&MjhPD|1{+V+LfTWLFAZ#8G3yeF9(5|tr&slw8Nht|PE)rq zEBm}EAnkrI-d#|G?q%%R2MU_tUiOOrIf!WkGB>FM&*6fUG&C&q$W_AAjcMN=%?iDP z4*ERLBx0GBlhba0Z^csi>XMcl*Cr3_&Qc34jtPGvkp2YN@~Xfg%=E4cPD+ZoP`UDt zTqagP2O>a`GXrJq+x4ZFhEykYLlkdSWv0~=zs`A-5G^p{FFM}C_m|nocM*J^Cn2I2 zM3%LeLlC^CL6QU0ROXesCT539JIj;rNawg1im`28KZA7m>D$>SV}LRa(;vGfNghDt zZ1|{QCf3jpW`4tCKR5r9v+db;T~@PNUwF^#Mg!-Cv@N};0gyr}(mk*?HI4m1Ble8D zev1}jC=(1@tzBMz&1cs)PnB!7%j}5$zSI_-9Of* zH95t3eMlArx!gM_s|+(L5kB3U z7;wmYn!JzeZKPSO*H*9M%&=Ovtuc=z5wF%NGpaT8$%Nv&L?Yox$#7crL5OY5nUu%e zU>go@{_&05R)`oy^_HBFY*`JZci$BY=!G?1$7hVoZO^6_Zj?`iw*wc= z6PNaLHsAkbOp8?!Klx!pGxSXD5+MXEGy3INz!3`OmjokEv3)BJoLj?_>*5XIplsZU zp>eA$cB*9KH$XAKx`*#+&6uXCi1Tu(m`Y^q01&55ggKs{ZgVd4&Fv|#o38X{uS4Xu z?*{lr(w2#lCUQi`Z98-==A;Xr}2V^nV8+SwkE3ohj?tdTn z{sB1na+Gi8FYaLHpH{!>dUy+v#oOs#F3acG5tp0NE-dPz->{ks9;cY>amkpCQK2ft zo8f`7i*FcTvwlt|yzJD^-=}?6_EfRE=c5F`1^}tr%a`;62ttj?jFRJ&5S`NgT`?6B zVbPr_y!675nUVt1FpTTUzB5if4ZQZLa}YH+x9yH|wFXMrTkaA)`{29Qm_ewFezHO+ ziJ)n+1@n}K?|aed=;T1nl@hnZpuC@@Q0;s-ZwB<(v2M(0N)5j-ZoWCiuB?j(hjm}C zD^6I6SXC6cUXXxh=;CQOXPaYD)}C~>Dc9l|uvS-(hU)Q40vSHV&`<}Q8~eEGnnvBQ zb3Mkn^iP@W6~((XN|Qk&ph@m{3eA^nPndrM-fTGWDHIj4i73(qgzIQK1PfRC$`uMW zxo1+EZ?WELP`T8|Wr4ouwzLq(X|>Q>;FXn^mje#&tu{U@eG%9B;+zF)onr$KcmL0r z?hB#jF3TF;J}{X3g5OR%r2Cr45Tv()ZtAId_u2CHVhcYgJ>=~?kf>aHF0fxaT}&j1 zlynOnIYGd3e9p!WJUySe?P7K&s!camH@|>7o#2|}={G!m{T(^lk2qTU(@`JzR5lr| z9b?Ly-QpJjw0FKz;8r__({y)#ah73zDF#suq^Tl^dBt~nQq3>O$jCh7shzcp5PCBs zE%`Md?}~U#ZB72f)CKM+(juMk2eHQFfNkY1fWK0B$zK^zxq9O_-~2zH6hb z3f0Syp&+fM$HuMF3Ix(NuY8dC)Ps_Hlylr|ZRdDve4K1$MhYPLX;S$*Kp~&$@~}dy z_pu(S;OX2MC}E6aXKIIoPZZtE*x1;7?DVu9tOiQcpo*Gcq+9ZF#rI82{9+g)O98mK zFxR{b%6>EIO~m-9nXYA3SGsq?Jb!dl6vO`NpcZ8&^b_}yGgY*%ZsPglimxvB@81WA zkz?j31Gy*o={rsjrG`(?-R>ROj#PPTZBBsL*jO^4NdFFO8B=1t42xSKY+#m3p z<2znB@vk)f6xy6?zW3x=thPa!Ax?VqDy<4gEy|$*)x%C@i9^FW zNJM3)r{|BPN}378{_ZhzOK0@)8`dIyhZG54QM$*5vM!Xxh!w+k;o<6-QCp`QI4k12 z2CfK@?ySFyV2Y879{)C+$u%H-gD1xsX2QXb_ zGI9Wa(n!5;4e!tR&bpU_Ak*-hM8$h<}YimH(0S{rk@j?nXqfz1e>jboI}%{WLNhpT<9XZrSdC-2pn|TA5#`K zX#hd-lMjE?MXBDxCZDTd;$~Ke)?j2}3g0|6`6!6e1{JUNMM&?T^L7k0tPb z_l~wO=@Xlf(0vH;^Mp{7S%yD8^v5It*(3zY_dum&)(kJP+2c{sBg=1uqRH80`_YKE z9e?PHfBAh?ab|t?Zx(WTGJ&+hcXYLg-_QN$tN!=~>)(6wmHb_J_s6gWJ|;enrw1&% z55GJ9r2H*G{;E`^2(*avWBH)`0&bd>rI>?1*7mP7_TPWbfx@#L?XZ1PRV*zxZ@h?S zax%;$nqOG|_$t1O|8fCsp{Bp>@zXQWKA-=wwKF={_{wze?`#t2`v-n`6AIk)8CiC%=OeT%KF(76q~sQeHw@PBN|JR@L)=xJ}=n_(l(Ov?sT;bBFg@Iqnp z<3IK!lj$WWxvzK%BB)nhAq_kGr+U4At!2h*KtRG4TWXtCzXG)fAF1yFGY!tX67u2q z_a#S33pDR3WM!8JS!tNfc{%_Ll_|A8>^B=`7zll2C!o%PsnE%V#%80P zm&gvNaIX9o(|auc-rq!ajg#nXCKi@?Vt?HKwHf#~)c~aZ^nHN1CBwod2N+|1k`A zKzs;MyWMJdFEShhR{a$`%>!vgo315XDJ!S`Lr}t$803QTUS%0HTq%=C+}!$(+Z9aq z&vFi=6@fZQES6Aa+!9ICXA#FX`--9ONC%O!fP%EYO_&kW|9t(ItTRmD{9D1=x^D^D zKaejkM`!U&ln_MF|N71E+7jTYNgr5Z_jKO~@hezrAK_cE8#~h4X?;K05bH!&IFtVv z6N4nyaV>&a2`#Vj*S}`wWJ!diJjwr5j(7Rro?ez*wx$W+xvPjJEE=ad1@d4A)RQ>> z*%kxyCuLHb-~SO`|LPf7?2)*B)_rhX9`RkK$COo6*YN&{Z*r-q?!ei*bH$k4--NPf z@&LrS>B*2??eKs}Jt7Y#e)=EqUWO)idyA<;&>?4q-5C*aNQ6v}m{DO0f$Q%b{#*6` zB_G)Nfd4Qf%JZ2$5Qlv5ZKB}5p9~2#bhVz*l58c;R`Tg#Y@hS5o%AQAco)(Bk+5u( zycn_AFAY5o3T|GZqWO<8{KtBe+5;kTN&A=d!+w?R^x$6ipiBCcDPZn$3PSj?3$U;| z&HpeqTlX`rX>6|~+Ix3h%@5oZmT~<2UoZ|9#Fl^UMIxbH`fOz1FqXx|h{aOd2t? zIr1R%fkAE3+rO3dm!~&Pz!b_`kZ&=7{2bQD!me0OPPMg0DLq8$J)^1)9b$@@R7}hc zcJpgD$3Xv65Yf}Sl7*nk1@H?dZaNuYBm5Y>4jCcQ`RA5ON~hX(d3WEWh+0`y(a?4( zynM^cNI~)B!w1!eWMs6_IG5KBh3N`^)Up3l9d03YutvqFKPDndj!8&hlu<`4?nZ<8 z+<2uF&G4?j_Uw`aCeK`KPc}-RsN@@2_oL@K_D^+k+Q1`O65wQ=B^rsPq6%;jlY-C! zV3QPFX;*9UOEX`*^TurKwQ%q&)5ll%7J)D5LRFOW!&KFeLny zfrDPHNUNCPTQQCNOL4tjt|)?k)tBE4n~KhkmruiGM}WEEgjB8R#S($by$@{+AD17d zF>!ggkVYWY-xry(mVE}Uejda`%ED{(#kTBrKAB- zDYLNSi5dCLTuo$O3?(#GKqjWyog_P48Gz%UKY`^pKy36RN`*=(h5LQ9ZQ|=d%z`hs zkLb^2i}LdF6g;H!U#&H768%G5{|6V7$kZBA3I7do59ptkOw{&VHiVWrC$Wm#g%tli zoxh(bvygtKT3WbInOa!9vzqZzaoG`~SzAjs7&W5%pUnO(_M)vwPoJu)HCaf(^o8W+ z=X08xjgHRAZmML(nP5vk|B97lp zDuhm@Seh21Qd3kUkDqKN@v~g=cqO_2fxo)moqrxMe}T&Fhr9SG<1nUYYd%@IB+a+P zD*t&5{P~ejp}yN+m9NF0D{A<`9VrEu80S*}B-;V?{!U;R-uRYnpN(8qhBB$YwR@%U z4XeOZM__2=M9X|8>%cQzOMQFmKmjdZ<#!|nfe37g@KsQ6o6zOy%|?$aL}`nbnx zMELq&oBpcAKj(#4CK?aFhgh%WcX!#}$V{aTFv{vtYJ#jB)?Jh2^iOH&Hi$R%JW%y`%%U z=sAvy#HIfKtCkcw!ctG&LQ~nn@)Kf)TUJ;1-9)2Z8gMd4n57&4eHX+3CWb^gP-TPx z)31zaf*yu3pSgFScEjP-D^Gtj4oa1Jr08fIM5(iJaBxHwNrl(SZJiqXL?N=$$H)br zk9r*^|0`RfILJA<)Js+BDfQNVp!44^ufO!C=ZRhl-a7O|$vR>~od|P477!|CnZJ2o z(EVYUhE57+p$&x;pmWUX-|~NZjQOk2f;F6P@|$cX&lMX?CL(?uJMl&UhEG@J*QdXM z5Gg$Kp&~yTy7sZ>W99CVR#VOW8K%FZhbe23*GEdJ!#A`lNt^*F_I_U1oq^efrHsDnRMqNc>s=H?WN8=e*+>MSlH zHBGGmo-4}$k_I|dswXl|O)nJBHE@1=fVx9BOQlNIo^^X0^BxuGS_oHKlWy#dRfSx$ zdq#!p>*aL0`mrC&yD%e?AhRFZRSoSGgd6 zu49it{mn08`}$g=FZO0+IG7%3U~Uh_jyf%TRW}j*$hC=`CU2ccx^n;D@4orsv~t!d z8sC?R9>uXQ9hO*YOU1WLNHP93h(Cumfwz0Kmqm?I!GyN5LLs>Vno;jme}1PGHtf=2 z-~7Ar&kCWy<9j7aK}v8$K7sd@gpBk(yeKNaoHz*YAJd`oxVwO1cSi z7`ueGN=Zr&&f#ix)Sl}^vUA{b0tX3hf^1IjjV%(?Xg&L3|7l7|q{q7fTA-}{2{7+44=HD#mEo3WgfW$bO z2i~EZh4Q<_pu{e|<*)wXq0w)D_@WIM8;lUGmGK)A%Uzlx-&kWjK7PsQ*wg;jj4#um zn9WVub5>O>WPyNxh`J~{c2EfQ{55s%CL?tW1M}yI&mBHSbMuOvsCPvvqfvm(d1beW z`MXV}DFjBtMDMR&grbXGUhf;Kj#tMk6;o;KBnw{`Xvh5L5=4DQ;eCcrY@F!b05G4T%j zAxUg8svXL|1^%o&!PgqOV6$UpFqu8Spf{O)pm@~VY3J&3NYGs#K8V1cYjuGcF*+uC z_CUD`#CuL0<;zYViO1>ghwgnjzSeKw;?vSd*ZNX#EwKp-34NKGO858okAwL6`Qed+ zyeT@3?FcErZ$b@|vM&{I@BBu(U}`tpU-9^l?e{~*pRMqO=uvm~N&TQ4njVbbOrPrh zDtBqN6PI<-*7?iY5~Ji$w=pm=M^Q_3_{_DUi>UoY2ElMdHQP?(I&*Wt!hSGRWg0PT=;uSYTKot9Eu zG{d&S*Y*b@%5SACT{CwsvElw#e*EH|+-RFMPMR9afWN5!Jv%vJtWmqYR`Wx;t@NR& zrcgqAJM>={>IK3Yi$p`TW+hq+i_3KxGP&>z?Fk?#k{=V)tejZ7-Wv$ zFRn6ploGUUqC8n`@8(dOm=vL?b07E+C5ryA<VGv@bRWP5`6d@YhY8HZD8-Nkr>G@$npf*7k0Wee?IaNR!H}ane!;aD$-a15 z=pHN{Ss@@xd}jHRrh6fngxb|8%m93}Ge^C)yVv2W*LuXEUuqBMqM%EP-Z zR6m9sdR3z05fe*`91$_1#D@i_?^gsLjYGW@nAGdF8w1qun?>f1dS4AUTtAAH07Z$g z9&L?9Y)aOQ;^s4FJ<%#lWnNgC6)o^WAIL4VBYt@LMz`S{;kRpElc|pw%J@=qxX`m2 zmD~+a`VH2JhTz@I_5IJe&Nd;FZsvaJ5sD!t%aKrz*lx~AWZXF!r_+4*51_h*JkN?v zG%qvfH@H;)`SE7Ah)Nth1dr?EQvl0%7^65_8ChACQ}~U_!7puhgBTB2_i!s$_Pi?3 z4^=~-s(GbWZzmub7WC_Z z6P|uAfAn;80sO3$(V)dVT70;8A~HsyU|WKLlX=*!GVa@8aJ{E#mNcdTyzy2@9uXC* z`Pqb)nlc#vfb*XU{pX6w?%e=-;-jB{-v0d4AXP}?RIb9o7+-CaM2gcZR)d?l|KmuM zdxA}bBaUNhs1oP4gz)nAi@+oI+XW*G@Hd6d?$ZD{GfG>!;OmWkIZ$1}%9q>4Puo0( zP;fj_Dqqx%&Ef?{lM8!@OGqoJa;uk%3a?K!g{xgDW!Rs=F;awAw4l8ir0x7pr!?{S zw5NJ{X-iVj(Ib+Yq5P7DLc46FL&wN!)C0FyXGIuQo;y^rYl2k3B~AuAfdt`&(*Ss?pF+p^YTMNm1?&`gr}Wxt~fid?=?jPvFQ-t{%#e(-T9IY;Y_zR-GK=CV-Mfbc2%lVAN$P}`A^!!c1`>Zgh#&7^Dg>tPybM;;GxoV=@y3R%~vOnO_zP$9)|Qd>5IlhtH%aT1Wz zzC`dvC~?(7rdO54lqtj`@t6e`#;`OB0I7VL1{_@)Rt-1OqOWn29P?tcI0d&=zvPy;-Lgvp}A<_W?M+ zu@=;-0aIlCNXkzBqv(AkdxG7)AWE6QFRK~(g6tjcUhjX!AevH9?N7L{71-`wPY?Xr zs9J%pY09XV$T4)hTwVw^2YVL+Bj|1Wm?|1xE}vy>kIO}v*qX3AeLPcKwo6G%t_Y7+ zZr_BgYVjTxB%yJq7GuQ{jSnp1VMi~<&p0<+g-OayWZxjwbWFXw=Gbe zy9&uit&;y4y{HBD%@)G{+(IR!3>%!qb!KsOOwi(DGSSl6d|u$&e;VLXl1O^hJAK9> zA$PN9RXja#-|T`P>fhVs*11t}`E7f6Oh#%|;|G+2*RK(Xybps$%phkoK{ro#wTwx= z)zj`uPP0}ATHcS&7Y4cw-l2Q*?Lv}MKD$Zp!ZVoq6vAe~oezl2C%ewJ93w99f~AO8pD{Pmbe4cKeLq2aTC7|+ZEcJ?9EtS5F5cbT&HuUJ2; zKl(+t{T8XP8O7D-uCSm1M_5jIQEUy1_Orb5cfW^;B9IGEFDH}tS9+w>%|O|eE37*2 zah}^1{fVfEV{!4`#9LK0H3hf@UlIPa#c{K7)rXnKFJh>9HMZJ94kbcSM*F6pDs_xL4r@2dUtHZ*I0j7*dKNb}B!DZKvw^mF!BO8{R*KPM z@(8^?lw|5#SzeZ|%zse8u>u;b(ot}#dD72D*dDX8XQ_r-P~K7n^LuEdCW5y)dXqhg}Nr^&0{s~O(xFUPr=CIj~696p8NvAf2thYjg<%Y z|0xwO@n;S45$^BgdiXp{apo9DwMSe3d4B!jMN7UzQ$J0WL9{lxEzleZo21HwoENW4J{ULdmJq9&0Dd$a4?-4eX*W|iX3 zz{8SN9r)Cg;_bOl!*vIVkijxb#@VgsCLX0}V~|x}n<~hL4bVj*m2--BvK@D7Y6_-4 zVG5@7AsWaCmy?rqYHXVDMal+qfx zz$Ar2d#+WMAtX;x3aWbc>QF-rK7P$;kOVHO%m-RiK+JM6{(IO0{`-yHx+L~YX#OXs|zOBAcJR7Uf1_WlU(T{0+n{kjn#b(G4j>Av_GJ^l-0|6%Rr@!!C$0zh`$ za1}4{b{!%q2@7N$oK9#x33nbPc0Bv#prJgwEBWqJ)ooS*C#8aLjxli2ah6x@E={uU z7IHyM*e!uyP3=GNulpw>LG{xmeT1^e?xi$6*8P>7R`6x}=U=`+^VqNO3mf`DgEGTt zVi<@RlPma!JD9)kJM}vh_B(lavyu`&SF#>SDMB@lTaow1vW?Z!oLXZ0l}3`yKr*_* zVq~IzW6BGf=(Tl{F@ctE7zS5p z<&U64`j{NQpuFEABR2V|KefaqBjtBLfAP)O6}j0$swnfDPY`pt{Pq*?DFJi~3HiAR zPAH9&kH2&c<{(i)5Jdm{>uQBdj@g@F;Xxc5a7*G zHJ16!Hj92jzcGIce;Nm(TA#We`;(FZWp5n!#{}xvL4icS{E_HBUeE)q{9T?%2Eor6 z1}MO#!h&Vg5&V0|z+MPiZr=M9ZGXB~G>w4qRK)m`#~HwvT_KV_&e08wb#W@9rqE1Gp9wMlPVl zd0RlhGbAMBS=A4t|Eh*x`wbL-ll}`pfU8Cjc_BpG+oj;_XWee;8+`(A zr0(Of>P8{XP8mqcE-%-5>lEO%CEOtdAWlrvC!2Y=Oq)w8VfXdV??s=U;Sq3W8z_9-tJ=Pm{j_D?IC)Hj~Q!JrjPF* z!Gsr{z~slKxmt0?if&GRdAujj$TPMH+62AMV@hXgP4@O-_G0w(^h`ka2BDs#4cFDR z%9ZYSRLa|Famts8!d|)#E-v}>^FnL6^!;n&;S+ekzXkU=kWSd-r2dyva^OP0{8BRA zs7Pg9-Kz`K^PS$aM>VmeX5$kAD$o#Uo`lcgD$!KE*XS&#!&0vp1s+_2wxfR{df{5t zP4W%92o3ME#+{Q~z}$+>%*I*5C%EM(_VqQDT`)@tC6mU2HB{3|9E<$B|N= zvU+cHS_U#&(aH>Z_6K{Be4#I?aWM7s69eb3kKzGr+q24!PXA=%waOF?sKkWp?4yt7 zqdf#z9D@;%y|jDaja)eVYTRW_S-^!tr8Cwspv8Rtg0rM1(TUi|$OxR>S#!8=0zZ97 z1!}o-w&JkUB4hrIA{8=|y?{rQT#-2txz!BX7xNxJUwOE6Y;)tj13 z(Qr&k?d@Y@^GN7Dk4bl0G$tv(Sg4QsPISMeMlN0Wx_q%-gLi7DQku2R(JP_8vF_yM zua9Wc+m7`wAV=ehs4GL|-quAw#=204dc!Ayn-Z>s?7)*8*Q&BX*4N=S+ow-4eg$z+ zi$5rU^S_P=03h^Jz)v58vTqw>TcQ=f6IsM&xPq=k6HkO*oFeZTLC>(>hDWmSg^ga; zqO&9mO)si6oDhkTaPM#?+icxyZl2~WcO^983H5%i2yD4l=g08jhiSya1)&eEE>1q- zoLlyQSOO5_<1Hi~Z3bnDuB!|b>OK07GcRinFRM!o#oyjvO8mOhJ+uN|ij@Xz<v!&=arfA3WNrJEb|Wo1fBLGUB*!&AoaGZV}vuM5p& zIv${#K{AI)9?+>ix*_#RiQWO>*0iUrL5Hb9SNNvF_O?xxx?otv+K$isYdzq;`n@)6 zKe54V20B+<(DCsx>cy)SB1dn94<=(6N(BEl*3^Q4Nv*#HF2uR9jq3qD`2(mrv;8ix z$mJ;Dx(@q2?P!OVqqZMk4Z5)mlDz=lz7z9KfF*8;7-DxP|$k z?Z4y2qs3CnF{^zrf1k;fhsQl;A1>5>M9h{_y?LsYY+53%=Piu8{^(^x8BUr!1H=jq zDVQEO#O8I3mAmzCA`X*7edUAdvB(vg;aD)8SPw+=gfo%(K`RWj^;|6((XsjmztFgL zakPk+R&?==o|^M0`PI%foiF!wQ|d8=Fo>KIU3L5IYj;QWCjLy757a17$mOTbcE~}8 zvszEz-h-xDynaU~r-!$x2t6Jj8-=!o$+a;k61)Cn_pwjhVSYm8wp!Z}wPnRRZ`5?&qn#fG7Xh)rzPk7{sAQX zN9qKfzxpzm4Fjo~1u!nz|j2r~PE%yx9 z$z?`lB45V8I6&+mow3}->y&pkl}Qgbc&5i+2el{ z_zTredCM=45IA3(Q2&K0ZHNI;wHJAdv)}*^!-2&QcW5<5u6e-KxI4Ye|aF(KHq`aCTpd zfr%+bPfsuSx+NW?uj_Gs_;ywQ022ijHASyou+A|iP=@$iG7gW`;C&RQPv_)JT-4nY zE4+sf^%2h+*iQyxd6bplag1bjb@d?*ru}Qpcau)fE-VQ-%+2~zk`ZU;K?`?tNOn9f z4uR8iUg~~yWF+C%m}e4FZ(Qb(O`dkN_$&I)%m5idZ$?iK8Hr0)$kPHn@mkyGf?1b# zF$yn>qa1`xn{Wgf-z8W|v>j4V#q3A;^-L}GM#ncc39WU6jvmg=8GGy>CPRx;^|x2Z zxAOMCUs3`(Gt|yak*u$j@nwZA&qoVRTY3VJV6dGm`+ep$4OZTz)m2Il)sXFykg}oT z(K3vYp@?!a!->N+F9u4=D1gJKj@Srx`z~|sW!rk`-Ype}`}%wnMaX?0Jgyh9sq5FL{U8{>y zuX%~x2DJw9x)^YiygNLDCG@oXfQn4W{oR#-R|HbPaLk?k>LqJVm|?2J3Y(ovrmnP)$e_@U4JdFQ~4^U+8vAV3E7yptF;7`ypZ&u+u`|9Vpn=uil2|&crTl34kBCrXevkPuhU>y>qeh^< z3WJc4zUaqVR}QDR0X&5Zx5;};MyqTFAbZLXV+V=hgglQD``^-MG`PJzrVs@6MfTW3 zcKn)!#!0MvF5}$cr;Qygp{x&bdW_6R=THFlu$0-XbdIL^`&px=#(9>CH=M3iPF9v- zs_w)W6vXO!nv|Q9(_3Q7-ZwR+ajB<)Jv|1sT-{uh#i9^_-v4aTwSC8Q$9e06u7|g- z)>XxuO!G*{uG|K#w}C~_0$lnMim(*%S?kGbLW@8wX?vK;ucRgbiB7pz-oS0vIn2`K zZu`>_&F78zr|esI;8D|wAia8d8Kty*puzox5C4_7#86^KA2N@%;UfRn~70FmD0Jv8X@bQN_>Z zeSZ;xFWcM-+c!~A?W(?xEq#zUR;<#D%w?^u#pFq62=Xp-Kkt=FskEzm7&7k;R9|K1 z?7&;Pdn^pmB9mS|TxO)cnz?G}bdGP~SX^?a@H`>I@sY6NAwk4=D%3k)kcZR81QJs1?BiPQCw=12i@9tXJfK18yutM>#f&s<{-knC*N1+(z?4lp z(bGMJ)5|FAcG2P5kZQ5%5lnUt33X%t3$$kxa#j@nfxMKGcw2#o25;GgBd?$%%_K=59b=#VqAnk_TQQ(M8}VIjJ96! z(^t$zusFmJPUY(Z~RE8Te+>kh8s}Cc`x%cf5lZ{Vr(# zuAm^n?YIyw5|x$=vi88HzPOGviRsTv!s?w1|&yph)F+O9e^40;Grz3De3M4{Y$Zef*bOvE7 zb2%0>41G;&kTv8{m5^NaS0|-`Oyy+rvJSDb^4eu%kTH^QTU1?SGde#U0UBaSJLbr^ zhEk5s4yvzS1$3FNSVmfQq=a+6_$~=U&Y8Bif3%YbJHV*^RxkCKc)TUGmDBO@TljF! z?5qkTVUNEqWYu%OLVvs_Dx;jIM!#8xzF)f#d7_u4trO_6lby+bO^L*?*2_Zu*`h2j ztLL*Jh9`(aShMm;Xz=*K(DY+cVJigFxmmlL{OFRw1^p-t_a(~ zU$_`Q)#bWsenKYXt=n)Pmad3Qx$=xij5d_;X>u3t=`{*&J}B#LOKJWW*|rR{-csLS zKzkbj>Li4RO!JvD660fDF55k(J@UL@yw15tPL*IyOp;=g=Z4ZPh< z=tsG|7f9g}9Y~*ZQdqvr260-RT*@J&`ltof<8xHGo+kS;##diM@BQVir|^8n+s&=5 zJm*^1DWlx{aB5H+AkI_uC7*Uwms zyAooTcHx)1Dz!j|%I+#Z$~E7YX9a+L40W(;{QkwBV{K$>6)ozej-uXkPltzC%DbN~ z63S>Jdv5372~-+*sc74&8~IjYG|JvCq-N}@D}Ua$E2fcXPX#UbWE=}R9o-~(X5r4J zMIN=%eX(Sa3?wf+5n62B5#TFN5Kb>UZKMpZ3LY3>OX0Q7zUw&^(^;ogR6Xy#)H=!4 zoe0-_G#xLNOlO$!1v{m35#Uds8zf0Omohj(d*l z4bA^7>)4KF$MB-)NC`eW?7o+0S5V#eG0+sT>^SGh&SQsdj!3$FXg&4)4rKB|MF6K3 zU=)(N^Q&56Gov;SP3SLQ3V|?Z?Xi`~t43uqOt&>inAr`wcwcFLlb{u9b}p#*z|&KRTNH_25m)+|g^7mPJbA8}Q!6kzPEq7?9A*E($@ z&&M!+z_^4GpHx(vU8}}Qg}3n@r8?)iQ*%NIu@^AxZ1pixT^|}6G;)p^Sm92uxKf^c zr=2;OEEb2R96=GB<6g}>O(h>kZL}u13Defcn`w`>r!^arY&=s>)r!?4#d20aZ5dzx zQmN$oO7{I89Q2s#@Thh2H%T9WY<-KVI@@QVFUXN8cQXK=vx%NkQ(5XpfsW2Uwj2sW z*7ilZ`}>vay@iD#u(rr++@^-*gif=s`qBlG%Adx?Nap7Zia*cXL`P1~PX~aUuRJy9 z$vaplRqahO_I81Hq=9JoL$NH|_8C>lWe;?~2bvUM3w(ct(*)f zk--iP#W^wMy9p$Bd=#U?ye;Ij7h~FcrYIP;r=pu6!9BxOBtfB7MiYL}M%)wb@z5EF zWo1n1DnJiUcUq-dO5;65F)%RjeOmYi(Q-Bo1#r0cvHh66eT@co8BKeLQ%ENp?oC?a zosdJf7!_=F6bb-ssYNX~-H8Y8Wta1VarxNfx5-@LQdRP($%gjeR-2!PgPS9-6Ud&r z^*&*iWF+NTOHlW&(z{qVK3{Gsnn65+2z52$Kd$CkHM3B7)f^!sP35LLX7f~ zKr&7C_MnYwzF*IKd%iceQvLF8(U;XVR}ikW#lxy_YcHmZ>ZJlx5}rbZ$$&UPJ8T8e zH&^}F4Bkh&hp{lhOog=)1AhH`yh5H=;!GOHWp_bb%OeoDODoSqaUysh zNo%oBp==z;@~IbZN1K~A$Z%TO_EI(2eeZ@i#&P$^%N}g$W!Gi}Jx(3zB{X3`S18_1 zynB=iTe_o>3!?-Rv2w5Ycptmdp}(#QM8*!!dbO zr(v*~Wnko0LGKW19B);Ogkbzv<=CBZPwluB0Yy+L=+OQn9DFd9h_xRwb5#~_`f}M{ zu&B{xXE?;eLV z*G7m9@cE}VqiZ7{ z@Q>DuG9J(5hI-=~)&}jr2IV*zuEliZRZv_WHV9r0%$cp3$&jyTVcv~+G%S(aZivl- zEp()ZH!)Yx8ydXNB*gziG`S=8K+rC0WrJ>OdgHQcj?A%@k+GL{d#WgYn2j(Iv4C)P z?I46S=Uf_;M2f`8tBy}hD0Fp*^kj}$GU{c@F6@hR8j+rzdh@q1+$+5a+ z#dSH8mBph|*9V>*&Tg4AewUS%-b;EWiCguUSz8UqPZlQKW;_LiMKY~wA$>@+Z@OEI zSD1CF;uqbSwi~e8cV-e4Te$9ij39vOFSDLa&3-p-C<`x(x3`1qzgBgzTj5_jKX_O7 z>hTON8YV@eJc?$Co>JWLW&7;O>x~GnXC73XybNJC$#k^7v%{WyMoVDYsKZv67WHA3 zbJz5vZ*^9OrqQ;-nscsb1b$+v&^BeHI}3p~Tjvne;`7f?bG0@X6Ys()eVik99%cbC+bRls4BkG?&CH z?uGJ6;IMs=5Pi}EkY2j-W>PyHof5dJ)ES;a%exvsbN1@#4c+;kKTulgi&Z!*ozUTL zH)$#3IU9?OBrE%us#Cro`GWz!{VxXmQ6MS->+>i6#DT^_t{{k=m{Yv1pm~6x;Ld3` z$8d!{EBXU;RRZUSRMGC#QEJ7~_$K3tM?ToCSZ!FDsl(SHbYUSN`mXx~jMvG)Z3K)P zUod23WMHo}L8=e^(J=Guu$KpoKZYSIuaMhUU)5=^)x8thyed+Cz9%Kl+n<1HZ6}}6 zNd-Go(Cp6LIZB~-bs2Av!k`aJ6--=a*2}gU;>9y2l~f^kJv!jg#luT$%kc-JctPwA zUO#*5>HCz&{Eoa}X5X2%?R33ax!a0MAFN+_D;~1^naKTMF^i1E8{epI^&tgmFJvS? zu+@1E16%@5NMK20&XA47oVL4V9JPw`OrZUwRyqjlf)E1N`Z6I{ae)PMLW^`PQyd2p z5gxR360mhA@)S5lye`}Sq@3+T?Qb|al;rY-He>e6afuZ621S_%bEK}Ge~6;T>`4>9 zeWW5;J(h|*DtrQ-Li>1Yyh=}SYllveedQ|$vrx--fAvn0Vl*kXe4SJSy6hIy@QuMF zqo$>{I1daf&XzkD z80n&u&y#5bY8W&ej)TsT1!p2(8^R^TrzZesm6WFui;JK|gp70(8fDa!PbQ_Tt-RD0 zW1z2>W&aooHnpf7n=?f%kw0JG@P663165}E?OPGyxOnzNhZ*IuIvUWp_bO9be0`X(R! z`q$}=A>Ed)IB85ejpbvz@!g+O?&-NiNj%q!u$iwyN#WbGg##xpa)CFtNk?6m3P|TW zkoz{3?sElkwm@V>98NbF*R-Ox(!00ZzHJM8zB6Z96U}OCqk3E(%V|4&s`GgxqSc}L zQQA!yi!X;>53|!6kCuWbBxdy2tT7;({>kMgl3IrGSF{=+DHG{5df6~Bv^@|%{j_f? zv5gtrU)l`)6JeU8R^J()k34gF(M3#)Yz%t;g0yo0Fqg`}3M-0nn0}eJuJR$CP*<|E z+C{DHWQ)Esi(3JA_%FpO1RtRl@Uk5VHz{;a#2b1B(FdO~zH&dUbx|J-`0C{je*I`i zLFVMWmY(L1J)R>nL8WBd$pH^~6FaybrGMnN)>b8CH%%2F;Vw^Nh9<*&2Vq&*3bA9= z(gVE-M8l%M4P3pH)IoE+Gx326>vhTf#0$wo27-KM3(~ZT!&+{uX}PJ3FqP`ZJj@sb z9RzGd0}Z$zZKuG~R$z%LOFO7*e2HwZHR+V7GCI!+>oyvSxNrlVJR&&somE(HcAcXE=r9Tzsqp=JeZjtPi7 zqR_YwT!8ff&=!jlxG1>@T#6^;xhH>eVwC8@vbvI)LfLk7-8OyCQ19T zwIoy!E|1$Aqgic|G-6@XcdLA(yRE41o-;hftR71xH1Lt89N|0PvN-x9fe_F~c@&GO zitH+wa6spf;~w_g>47p=8Kxu=tRJq+{tS^P#T7{a3FF`|bOCE>WV+v;jgvS#ye`5$ z1O$?LZJNaE5X0Guhn~mY0rWw;1CEe7fSL(L^Ej8^>S!s!^${WGXb@IjJSlK)Ie!C` zdcZ1^Pi8!xV4l|DF7+p-)~-z1tDzWe+Z zeV|_`r$-!%L`X4i!!wJSY3Bq8O>E9v88kA^AAHz{+t%3%L+uk73|3$1wWc!|I<~&h7nDoW z*G*I?_RamDG!oiF&6p%~&eQMs0G9`0y<>?-YVv8IK;np7beMJNA7mRM1LE!prwzcn5Xa5^@U!5`bvZiEvqsXz=!97Z)!rg~YS6 zPNC27CC48Rmtc)be~Gv~ObHX--3G8xkS~QWx~A^#gFPUDTiX!u2{uTKu^6RpLS(Kx zLU~5H1)W4-lC%&QeE~q$NpOjjQhSJcu>g$m)9=bwt+0B-ksR9%FHp(Bx<>CDh=Rfv zQb(_xq3$zSSs?hO6}B=@ecj5P;W(k# zzc1u|{wyV3#E_hL*HOk~FzKs~u%SV%%*>#v!XqXU1Sfy6s3(yB(EVMmQZ_Bs!M7VR zi1|G^oAN!%f|n!_qHCW#1T!$Bht zL$i)DyNa2SvQ?}MCsggc))rQH&8sI)JagPHY%yb<#U4olNN)Azhae!6gQ29SYsNLm zTkf`j`I&z=Jg~7x%Qnxijb45OFt=M_uW2o4z9(Kne=Z6?HYda=T*V4+hG}8f{t&I0aPHjB( zPj6blygp8;jdC!WEN;ErUbjfp#IOZ}rV6t@=J?Y>we0tZ2|U{oy%*sH&po_>%*3#s z4nJ$~cpHg`Qz6XyJBr0uF@@Q#6t~>@OMwL^ho5R|8^@JmOz9f^>%2cMV02F#y zK-BWc(+tx~0X9%8BN`goJbA%snp+Z^GkISM#Sf13fs^A4)Ph=0L*Q^?46K#mE!3`d zvM)NaH*s)vRF`EMF@K?gV9mcs(PD4b>}+LhKH*zUQi?l>Mgwis0h#^duB)}H_cmhW zlI7g_8pUrRjo^sd!TC$STgU@BIYl>t-nZ7HCRwMRvRiZU^xmW8m|n+nN4)xO3l;v| zjJz5ZlB(;|jORPawP}YzA2=j!3zt1F*{8o=OUi91TX-|X-`gCwd}MF?az|hlCFy6@ z#b8slMZl}l`5$MSu*dU_6W``-YqtchHAaRe$|`)#X}V^?fFoCE*_D8RJWH!qiPLB- z5~8j8XWslxGS;Wgn|Lnk-G7-6ZkYqVd|9VJ^*%kX^UE33K?!Ak$3WZ-PH*<=y_2c9 zC7?d4E(EN;++=Hf`Y98*j1(1aO8jw?TAo-?t#j#JO%2-C_uXPac_8~EM!ta&moxm- zBX#2`GpO}}wJ2jXdEAX}`+9Idp5Fo}Gnp3TGw+|Vu$4$ruPo%#i1VKPlq3k)$nS~L z;E6o+Thoo=!jd5hY%)6Cg{^++x)d7us57tSpdp0$Kw;t|79(DZzFDy29&~&)yD6Sb zR}Ex)F#Z4;RbMO0*&+r3yjewzoFJi&Df*EV2G*vY4-RW3g? zA%fXETeX+3;b`qfeRM|~Dof~4MzaVj`JLPQuNOw&0Pfkb<+nm{N4iKFptr@sl$^=g zSN+;&6IWxbNKp+BSj%uE5A`UA;@u-2dk{+C zn|2Q^Nonce>roMKf`U>jBPhEIdg!LzVlR-6gB798F6h1AJAW&VJB|ZrojJI%N2K5) zCJY%;gT=gsny~6Mg5dY1PhvW750RdE00l*6SG^zRsUD-@pw~-VUQK?EeMwe3nRA`W z19$Ma&>+ALUwo7XoctdS?a%{nHCzjRp0_L(vi*-FP~PN3s+Ihbk_US!Eq_-@|A%ej z80!*CoiC5=6qMMIOP%1Ord>z?sOwtacHkhSVn~~KZf*{|o$HYU_HXHq=@#NOnY^Fa zeXo<Fzy|kgXZu+tk zNeSu2wfCkN8kS6=_tWNWxHtiK7_gbZZD;9ZknOlZHFG&z*U5vSBNA7bLk9v*Jpz=a zblzX=kqS6>$p)gLp}PqI6(ZIFKK+WB7f<@qPKxh>r66Y6K+-c!7Lc!UFDO_MY6sx- zOLQd-7&v0Q%7S_7&H`OuZt>_aVk}|$5unFQPdxh5c=KDFEJ%2^f*c{{3eQN%(#^-) zsdBV53F{q>=)yAvM`OMf2CtT!9w>|C6c#e9Px&x6dHm4TQD*%FlonZhF7%EQZYl#~ zdik$X@d*b^dw-mew`qkP&Wn|(N_gC)ypsvozgq@@DZ&Ob2!EDEtg`GD{B;+=Um2D_ z`oar=KU{F6f4Sg!Z-DZU$?sF1Sdhh2A_BvP8rwT-Kr9BnKZ360rT3Bg2frc4DT6>& z`O{a!c<0VbnRE*DC$n+(k6s;R`V5IYjdSNftv#8=(XY?(Ry7W7g^`YN4Ed2+#!IYR zIL?(>#_iQ|zD9Tq&TJ5Bj1R4|zi0->>>TKR3+iEm z2YmEb-#_+x-6;8@6g{3*T`dlsS^Df?Sq)AK?|GKi9Az4G3;(7lgdbQpoi6<6iow7` z2VUC#@qSMC0-F3ag<*}+6YDY4=J&iaL&aMBv`=KcXSC`n6m=TH*R{1TRdIp z$rFz|X#Pk6{*3JG;oS`Y*RW}le~V*Y1l;UA;h)tOKht7?^h03hhdVVKVc_5##1X&u{sS(nLE7ojuez-;*aF&6 zjkAb)#1m?2e{#XsIbkF`laqQ6;8+>Lo__0I1;^`;S$)n2OjR>lpA)CIEV>f^=&^E7 z=-wC<@?oN#>W#k+RbYGEe4BPb&8xKZtJBgjXi2-EUq?RHlvxc3HwNw4>r{d%POcoc zs?JRiMIu-E%>i89TjM z6?}w;3J$VkFRs;2d90@ty#m8>YitAJAP%|5W)y>Iu-rw}3Ku*1vB3t^lZ=rf9f_Mj zOuWIJw$Gmn-8#nc@T|^h)Ar79l7Z$yKnl)eU%9jezU)*htVn}NWg`It8Y*JyGgI;W7HGME0WpBmg>RmHu`vzMc=OnK=w+1 z)@c(@0?$ak25ylW?7f@YEXL8MfbN^;4`xWZ@bF@4TFZXgs;LUG=U8WNGXynyrUARb z`MlJK3^HBhoC@g@v8XeL)83VGJvI}2 zWei5AHN%}E|6vg){(!JQ?{N4(bJVg~1$pV#aR(i_&`Dcc673RODI-}leZp9p%0U9h$qT&dPCy6->EHtaPxF%p<5 zohTcIOhr|7)Cng!ygO?3}Y1*aHemOeq={tUhmNk|KD{*X#nZJ_Zy6eysf$uQ~WQF7Vt ze*_IfyF-Y#+WEV$j*yxi049_aE(tFLD3T)C~U5 z_QQA|dBk1!Pa5zN1m{pMy1N2SHCiPJ42QIZ+ay!zBXFAY#MXoXYtM2e5&hXBe}E#B zzKFWGE-Ouj3|sA#K?>CM%F zxOr;?(y*_;zidh4;5INt0O7C^P_GTkPu#PHfHi)*F24r`XMZ%q`Al8Ei5-B>pADw$ zg1HDfBoFNrG;caj;7MVdUTKP2?S>zf)N#I_aOSYyeQQ2bk#N7%tcV$QWW58OkF>8M z#a?L?SKDUM$-2w(rMi;d+(%-yqJnFkT29JByo{IGQnd4popN5cUBu<#q<^EswU%1E z>r$agcI$QYP7O1Ui734ZaqIf(%DmqPg?w4uLi2kW0KYJPy8OK=X6z@mR*gy!aQ&p1=>GvqVi4> zEDef&vuq>wO#;lW#2DA8L=4{L8roxeuy>f@E<;a&yZ@g01i8&!%d5 z6XbYg>0}7rae#lf+RGPFw@!Qjm>F5=Q!@KQcEQEC_B}Rxo?K(J31@AVds%89y>b*l z(ZcIBP(V_DdS4NxKnyTWAc@2>jTW&r%3(!JnLvQB^!0cwEO`eN&Bmhxag%*_*vLiQ zL2fK`B$)b3xf#-ld$O4Fv>Ck=aC&!V&}NB#gctz{5iNtmPB}e4C-O+&-$@jnfjO$P zjmYde%GX1H@B0AcAFBpnluP+7B{?cdElSbX7I=7gu>r77x2>;wzZOD!9`((A^w_w9 zEOdJSt)TCR8Q&u#Amjs6-{X00PYgis6i}JcoGiEL>>0aAA|XdacGwbR(PVHPzaj=? z1sLI&c7QAz(wt zR*$Ya*4cj+tVBD{Wy-;Slqvq!RwiBb*d(vZyR&SO5fRxy`F4-nN+(`50o9MOsv*X^ z#pI21YIk#Qi$0geGJ7Ud*^jg)<3z6p8AxDd^C}k*hMrc?-kOKa9MC571_3=}83Rnp z9RqXyK0OipGTI(E_{Yni+Y=Byh#o6H5fr1QtRABUWH>EHo5Yz+Vi^!>{c4(U-#}iL zIh>^j#92giqIr@mU^ewt!SwYQGX7*-^5%QflwHkqL^mkF` zupFOA)9^jXGK1qR6?G(i9rI0lXRkt)G}BI>sdK|ML4uZ*Np}ZG0q6%cK9fn~$&YBe ze{LgXL_hTa4&fvL{A6|?#%MG|Rnl>#)tmkXH6bh#hV#VEc3jxrxcYibrM&UIzP)9cHiccOp>87)4nJjgE}PrlWKLtT89imT**t=A2vJ+xvhb?3W3``w z;M!5JVRz~KeVb5HkSE}6S3qdpZGo%sQXq|N^pSTPgKx~b6QHfqk2Ih@YS&F0BCq5MVy};clqLVhXaay0VlEhmZn8rkZl5>UQ#7n3|Cy+jI z{TWn8XBpXPY$T}4a|pM;@!^a+_@vV1C4~fcy-4M7S0~!(lJV)|jb*+$=TmNK6B0Fd zWZ#+YY?(tM!1OiKXh%>Wwvo^Y4Zgb4=rOn;A?e6H`TppfzC1Bk=XJhYlYV{->3gCR z-GBm^y9-f_acKtR|JA7>{@kfy^B7-8ltxq%`?ZXIC2i|=^u`A{ zr);TNDnjU+!B*+3YCAtQ7dqR#^J;atjrtP;UE5?qu8t-58<;Q5SqD0SMl4?!#m=kkq*a3j-r*__ z=WDQ6g2wCH+v8AVrAd{C_Q&jy9Ia;R{FmH_!;%5RervwNOW^sqO0L;#mzowVM?AY2 zzl!^r5eqn?iQ583+B7DwWXSRHC>n>&J+pOUG!zMQcRQH zD0SziW7BO=UhU^#0aVvLkXD~GoF6X6H`ZGANf`$Q24?tVC1lQB5s0>+D3pGNEEnGV z0EX(6!#v24zL*5$AW0iXkk?)U-4lf|+d`l`B7OdaIX4=>TP(DiB7;N5C{C@8kF7aK zJRTdW#hyVy-G+Cu#O@rj9WpHK6uPyvDhd96P^+aB}b@4Ezlk8 zmN7rfmF{DQSI`MS3o6fztb6skKR~iXJKplOd3`N3)>eLmFPtA&<6vQhlDGvu=piq0 zA6@qwoCxo(PnfDQajHI0E(>B|VePaSWo*MihKK22)|u6VL_{c8E1l=(Fk#HEEwLW& z`f+@cf8ge#O?`VZTn=DgY*lofujzTLo?Bn+z1G3gK4}eyp?gW2z&Al>G{_oI+}!2f zt&^9LL1cOeKlh*G+Fs~?^xgmGJwXeTe+^Y_c1s(EH$T3vwm;(;t5$Pl{;}oD<9xN% zBsc~os-6Exc(QnjM0`__&9&E`cMi1`3D|na@VHS%uw8PSF>|(ZZt`lRz9K9!ROQykGS{p-y2=9FD6gSbF)6XRJ%kMT*+yiBhLrst~bCu+>s8z`3hq>*;eBIVNAn*S98 zYzYn_LWFIhES+AX?x3)3)$?4QC=q~}y@P$1ECvk}a=G3iq4K`}nb`uedeHw63e@;R zgCyfwA%jk;=)2AyfVIQgw-t+NoQmOUIRi5raZlU0F{WJ`*B)O-1Cv+_vSWo}u;9@& z@3c`{=0j7t5`gJKVi{H^sqN*7+c?_-$wJ*Ro_fcOQ^Ma&xR?xJ*g@C;S)?O15!J;w zr?sDN5-L`0N0Dw$H*+paoV$L#bT>%de7TEsX?5n7^QK*EeD?S z*K&^qL1W)l@gq?U%u=O=O}n*CP2&e3?FFF06aTZ#zE9?DPtvh%*Ap4}3~9YGHiW1K zJN^-=!j6YL%VHD)ZV?i|p>zKF-md&QlH*;1(8L?aE%KdEtlgai#~TSiXQbS%WcT-D zR|X|P+7>YT6;G#48JFXBaOF>#h+ogV=!@!J@WrcHT$NWV@|8u55nWby|76g+u%&LC zlvR5gc3uhK7N8}luhzL!lFB8ht!^?2Bo8;qkH6>Jr<0 z$lODK%-1{Kr?K#bJ1G}PFptZF=hD?;e4Sa}FeQC|3%3*jTO@S?eRk~9`p$h-Gr)M@ zusI`b48^6gmL?6yXz?RqfB8#Z*)S7~IJweMSF+W}<(m*? zLIA}I@xclQcsl6RZH7vHQ&LW~2Dk2TWN_|^b{Ai`DRq5%Qx0BZ^LX*0w<|N^8VNw8 zqBDJ3T3|!D^4Kijm67=-|M;*mkQi5K)JF!*U8>PW4gu1c*%R*cE6s?b!hO}MKt@yo zxuDSeVXf9F3P1r3BvXo~o16#|jlEd@_&%YvJO_r6=lpAv=08Osl%B`QyZsMtaQG8!0byGGoQ0fiC3NHH|@Q&#VQmwNH& zKMnO~K_4?sz)aQK_`y7Y98#7I`C0UjkQf_L%HhSljOEY&?dxpq^D4=0fUm=QCrmjs zkG=*g4WjxA9Fjk9AlNQn-u^io0|5dDg|SYG*3y%}*Q^q*lUdTU82|%O+&KwlP_fp3 zCzJ=gcQg5)Q@uXH=)a2aQqnz9Qk&eALjjM<$N&^L-UF6ouiPd(T!3hUyrF|a z`b)>L5|GwIAU~(wg5OzLTj!jTpW&p8SB$fPn3?Cl;QU26@_$GZGm)P&Gf~vBjQ5gT z*NMsK@1k9J$17~D`tc1o6JSN4Ud{o20bwme2#5dkDZzmNbeP{nR~@8H6fBO}CzM7| z3MpJ{yhD==x%UlBIC#HC<);9lPku=N!F=)D$@eW2cp!g*_kU5`eP+q2$+Z+y zV(g)_sI*HZW69;!4A?StQ0?T-IWSOB3nc~qDu45@h(%=l4;AI&ZF7Jc`NSS6BBM1P z#wpX~w2LUk@J9RsRQDPOW#oBqcj=AopVe(55Uj8 z54!_Q&u#y|Skk{y>7SUd;n({F6B(Uw@gOHmY#=f+N#NP2A${WT$DKq1^`vl~7Z^3{6_M;_#Y6P`5U8RpN1kiUe zS|KY~jKAooMK=Hk*LM%KdK@E4bnDz4Eo2jwAMm4{L8JLHQmYmI<9GkR(;)<^ z>n^l^%5>xGA4~ClOl@%5S^!jxQBoM|xq*i`gX&`e$DO^xqSjYTi3!|DaP-<3!g1c+ zCVu8KBDa3d}ej*+y>0Pd)hcyVI-L?Vyh>2F>8tiYQ=mvP1mR>zk-& z^dtS0K<0qs>EcAnOrbRJXJ`8+1$ecAe8MZ3nElsTPw@juGYO}g17B>;Wzud&2b+ee zNJz%?2`K&*qW&i`KED96&~H9IE$1_md2bgfaMyS5a;b>ZhyzXHd>Lz(xt;@HDNW(wOk{!htvmK#p7a>tV{AF;@PpC z9}#+0n{Jpc-26oa=%xB|#u#7vCY;HWjb8bx)Nj0py=-R(|C}f8%XUyXF()S{Ai?uEMvCeX zp!xigL=~Z~@so?_94o=9^D(;y{D0_LoL~h$a`Km%5n8f=!Jz~}1O;g=$N3{8$Ja;j zra&tEzih-yfo>TPB*z|emc<W_L5>U#>3Fe#HY^=)p`z@x_!EeK`^?XOvnf44c-Y?>e-+*Q6Wg=Zyh{F&i8JJIMWfN3(woj-Gk0qh zoSWas!ka~rK9!@_J3La*S0zY=cn7V}@Cx|l=7zu;W zivQRKyiLz^V^hQE9R>u_3Jg&ul`hm5URHIf`xO`^Or9UFkm4{IAZa!cI7$u1eoJ+P z#1qk%Rd-z8yaAdhSYMKp4b!4XlOsD$+%moke6S3((;|qaSAOA~fA(>%?=C80r}X-& zy_3r29-Ne=mqwD3uBul50*dH=e~ziqmOr{3S`_w;kEdQgHR&H;=A5r;+k#F%p{KSu z2|RixOmo2w8+E~VfJmvqFi;e)HDtkxnbIyri(IK<5#eI9YgH#(>;PP3$M ziGu??&KIwr--+85DxFX{thT|3I4s@#(o@Z`^fJ;wGdsdeeZH>yeY23`O63Zkh;XrK zDf{No*EpsgR-XvA5-oJBBkhEtQSu~I;)o6m3c_k5thiv_;&r%CwzMO zcPXL=k2t@-CPzUO!k+4aJ?D-CYs*@bUzb>EVW_Q4bc#e!QEVovIqgoAA+VGsxD0SE zJzjqp)NEFBq$9_-v%nRs;VGrTqwFX&O-F^Z(G#~=nOIHK+o(6YeZ*2byO?ykPCY^8 z(8>hAgd&REeyyPi{@rAX>(XRr4Jc|uA!4I_kQ?O}i`!*M;_-M7RD3ayb+*V-DXo02 zGno(TYE#7*Gnvx4k;x@3ws;5rOg)p)Rz+j3mh$CRDJ~>}Za%_Nx#LE3;8}fVUSE0X z_CS8VWpW~vtED_ZV?{QcrZtv@s8eEu-tOgVz88P<(TMq3gX_3+dck?VE*6^Sg(7AA zrEAX5ikjeE$_uaS*BWAA18L@q18>pUJ9+*49?G@E%HJ)8N-#%Gw4%nVpb{&SbzrvB zb?K94`aPpT2HWXIrb|tc{0jfgMvZ0dqa;E?$<&5~7z|!oB!0D4bKu>Gj?7(*I=IH7 zj~Nw-48LUQoSRm&hCyrmKi?Bey$n{-u>%Mo1Mw~hxF%_kJuK%N4>VA%ZAwgQFK1{% z6PCSe9K*9U?Ruuw-bq{EHMg|n+$BEp=HQ#M`^tx(bS@Ab+n|m-vH7X*D#XqV2Qc2b zsJ~sgvSHw}{M)y|M|1*fCEA+oNFo%Q>WY5GJoK8ZRyZ)PI4ZyjfjV%Ogli(&A|Qx9JXTQmMC6iZqLUQ z9?O}qtgI9?Z?QtsTr*-Iza;f@@aBV%99P2Tzw>Ei>SBR2#`2XHr1!C+Cf2h&Ab`y; zM(waial(MxhE|aLrdi%X<~gdK1r^n^y#QqISTXk?cYV77f^Yx z)3`l}vq-ryEaYT%M%O6OZ;lxjLfp(&>2l}&p4ze^%WM7q{csq*@8fh>XNBmC_FNr7fi4V1Zqkt6UM>Z9cV$54K^%=W^pe zkoz)ZhQ7S@LF-FI4lbPb@!K1GR*`YoTt>qF@ zK;rj{vV|>`-BZ^8)wq8+GtG8e%%O#aLXOo-$Zoo7_-J>(@^002h|WiV*2T`w zc)n88Vfsu0l&xXDnQj^{2KG{GoB5fRl80^EL4}D*nRPKAhfnGqyUj60Apa zoF2lqBk?RDspd}RaK~3Mrjy+g${@*m)9^%N7+~;8#t&f=%QUS_MZRNaJ@L%$N~<_K ztE8jQmn%m2q2?k{z9&FPu$k57R(GGdAq?|O@{W$`)bOSFUX;JZVt59Ej}53Xp_>|7KR;k_i;hHx(A+0*@=+jNir%(63bQ-ND z3|(pDy&#Xx(=E8zK`yGvy6yKR?zS3x2dSLG9fg1#(r1s8p)F^t{R!*}R*k9~{C88) zT?--Mfe%cRgRT}q>Zsx{Udl;)ZZCg-2z!+Z^AhF7-+c1left8@N@s&Y@NXzSO%3wom>#IHmr(UvrKvTjqzmXudCO#&F~bbFnrXwR};5ON(9^vE2^D z{^C3D^wsu6us9YyKMm9f1ttfrN~QlLAx9p!RQlB8_gUsUjkG-$tLlP1@59E+4A~Z( ziAphU=OWjqsG|1*##>>>>JyE7FAivTJyKP0jDTbvu zeB3&Jpl=(m{Jo@t(M@&rwGINd`c)Q5yjCFaL-^eILOhm3G35n=u-p75d#%HoRIpMYv9%hv$P!`>z1 zRCvxtJUk!Ia<*EdYK(%Qi(9i(h!-98U9g490TSyJlEhXd0NsVhAeGQH%WJuVb9LYe?r2xXdCIj7e|z*ZjpxJF6uPO(XP>0lb`kp5TQz2PM@`0ut8ti9>bZEf z>LOc22uv`Shh##UdG^OuoVJgQF4t6(E8}U7;d1d+J*@dCJ4pKrs+!8Nr^A36OciG7 zN4G1}sXR~>=J@5(qnw3AbwhUQUPydd_G5eMYRj1_Gl$14)DKANT?uk+m_&>e=l~co)-cFhxuDepbplYOr#p zmu4otLYkYhDJa`~JDNK#W4!X>Zo$vakL=Woy8{%#Z2Yw|IFbMBNh3OcIT61YM8p}B zI0ciCO;xa4%sL4Un@bu)+Bx?kqc8i7TF*&lzjerB@qkfiCk8HvXABrYM`vXpm!K~`>6sscJIUg`+%T05<>I!;@D1YzUJiM8zU%J=XH|(R~8eTJj*fzRs87pgsU1$M) zA#Zs1bw}&eIi1NJOYuyQ96VOfk5=@mt!7*r$}rxSqvUFays;UW-U;3tq(})v_ZqWU zL1H%C#T!kyCUm$ay~~Mum|zHUJLYS&*gzY?FKZ+CV6Q(_XyIMfawrc0mp4rZjT;p@ zgcZrncOkK8X&LuvgEOhT8=)np#`2D8`&ttpCkifK+M6zEa1X9QFw@{&TdkIV!QpV( z1ZQ6V`bt-6n~-lc35oC4uJsZ!1mqL52bX#+eX{iyS`A2x)Xzv~JUo!35dwbnMr8ZC zPU7yqKe*OolS--(Js>?g=EC77`wouC=fVz9)1sDD7-_-rxb z{I%zrxX2cTGeQ$3oJoUXSk^&&MW0M}EEvs6BZ>7sw0SHqPOsPd$18qxu(P3=`j%gK zv^OWgI32O}FUByBSfg_&R*4HoXLU)P|2TzOl%ZD|KPKJXj*Xim~8O;%#{ zJ2%^cj6WtVGZnSX8O-zaIUQ62tu}-0+t=RG+&j}j;85tSQam2*DGe~C`Qm{@lq*Ku z6YX?hFy*6UaZ?$BQ59Zg$$fS)H!>l2UTY^s!B$+&RRVq!mEF+03$7!D9~x0E_7=Hm zP3~KzD3Mw*rSkjlds3X~ka1YzR0@iuX6;K1wmhQKZ5E=oK7l&zOl8t<7I(8Oxmn!T zw!BiSr^TejJnw5{;1Qf3r<3tgeFo@r)YTQwdyjm|KPVR#sF+@3Z+2}po@3MX{!DX3 zTG}hFNM&{>zBrr|Zr!>Wewk3Vh%zM4zRnrCw_@8#IWW_Pm_~&#& zZRSpbbIMEhGj!9WDXS=wF~XM#?%3?cmpH<>;A)KZs)jPs+cikhh$zf+~7g+O7_YRd$K_xRJUeRfskEhb9M zyC$VVXb#28SBYengH;m`e608ys_6+_yVQC|mFl9SBz^^+5lHI?2jl`^`(5tXtgL-o za`ig>B)bSTdJV7m(pjpx9}lDOZ|lSE1XVf#5x=6$?_kOCZ(8_@qllb}6?$>3)&vf3 zS3Rowd+}ECYOdg1<`{%kF;7wx(_3IfGhN*kX3G89e z#YVh&;~1|)psGKj0-hBNjtrXZ%B&WCyG_hnUe)xgF-(0!#9^pYH&*zRD&;J3b$lxejm&J)qHTZGKsBJK&s14 zmm);jO$d;$NzkZL8HffHEq7;Y6VZiE&U!4I#HS@&6JnHzdRG%KmfZ3;R~%T2)MI#x zRixo*oXi<)Qf$r>6>j%_G4*ENQa5Xg4+*8k2#cY{e233h;bYcY{RkpyqUi9G#w_LQ zl}S?!Sw6PV3%HEuuddqaEO2d$+GSak-Q+q-hR=^Rz2rgGh_BI?NWbBR909?4?(Rru zXWHTX8k=3aeo66zVuruH$*-d6zA&MnOA3*#3`ROUB-O43>Li`Tgz@$D?B#$#-G>iy#m$vn7W5O1hT{%D#vVSAaP^QEtR-wS-j zzeipEz+w0p-+i;9#$L(ZjTInj@RgrAZII6o^5FP59Cb9)|;SpvRT%AXR_-q zFN=bwH^}cbQx9_7(0iKMol#GPedrVQRt-3u$+~1N0#*x-gE>!?mb`r{mBvPvA7YNV zdHDQ+0%9PsXNMKPfrSuSG=*@4@$c9lO?&ec-nP<&Lu0Yv+J6p?S9e%8o?T|TS+V=I zU1+!cOKicO=gVd%%w=pADUwc}{b6vz%(8Dy#55$}OKTkT7jBqvIUyEtYnnhcX4_(P zhGP+%N0m}3G?DY{I$yNDk~vgPv!Y|qe+#A;jzzAp-=c zJY$pV+t&31X$8n3##I!qP)U@VaLq)Z8@sRK?q8$-b(_0F>}~Qt~R%%hUW~Y+NPcC z^!PYZcbbm4)83mPcOMV~g=;rrz5h51OaGL4FjEKrgPp%-Vg}?(d9vsog$x&U2is;O{Du zl^THz*Q7g74u_~n$OzTl64p=wXL>r3QE1U)(()?`U&4#KGP}XFnk};64!5!o%Isg1 zX&yy~Btw1WLyW?*i(e)hI58u|7}B{Dr$jH(X%1xBhBUaAxOYv$zulzMqJ?G@HV8u< zQo+LSM`L+p% z9=GYnt;z(cqCme8#i3-XK10EyjqBoOm^ilq_Na%rE%aRgYchjQK{Kkx8?8-v4Y~a7t{Rb zomBcctZfcUqbK7-pCn9`hKFc{-q&6QYDUjG5ayVBTeNAFn2^vI3XJw{Db%U5DW1z$ zxsdOUl@<<|?&9UqW_l{VZ*Ivl11~Y9;M9meYUGWml z_lUeu0;-TLt5{s$yXmqvpty<>+-tU3p9ZbWUS!*GGW3oXAm?pOXMQmK)@GM&GMZD` zJV4AggDowlX5A5b)4$zo?n#yKwoWae{u3$1xECqqgc}W+Y7LbF{Mx>~*7&K+uWM^k zAT=@`Ty>)%(PtnI@X`O|-_W(f-s>?cqGk9A27wd&JYns>vi4caKG{BOE0lsMh!BbU zdJr;WQBe{UEi?)O%1(L9)sf{Jjf6T$7kB(7!Wr{zjAbB2=L2?T$1Cs~j{M zQm2TAZhd0qzgO9-s>H02P+G{-ocL%Cd6+FB`Cda-J-Vs=V}?49;9ZkGtJI&)-r9;QzB`a(AlG99+@cIeby?SY7V)5oGO*Ngw4lG zjATeC11- zFuw*Npot=_D1_*E#I`x5%uV!pW*PrhnX=Ot-yUJXUl)Ov=)E`@X#c9lGj?%XfFZ5A zw?|Cu@}9*QliLj)gZ{^>g5p8<1b%Uw}tu)!wDEL|6cxEzme)hkp_4K~Cj!_w4G=~G#TB%wt4{v|GBJ^`CT zZjbeCF)%CPd;_6t=dltduxy^ zilPU?AQbPuO2|E53x=hfY85F~53fbCz1!?*XMUOokqU12?4JmJ&T^pluxEOpP^Ko7|YJi&PAAsTFc)lbAK8GfZ|=210;kF ziU>SWXkUSdXq8aw{cCI>6FsY#OCqPc*NCIJrHI4SA+?3x%m5H)P)u2b)1sDgaE!V{ z2T0!9`>^K;X`0xg+Z!}yuw_L2{yT;5EAb+&29k^Yd2s?0&5hW-_}cJr!UXevJbmfp zseu?Bv>O5mN%yh*m=z&*Le7;6QiA*wen~~iz#_1jVFFL*2D>dU9!Zn{`5zWLJEm=s z&|nl^rizAYO&}zxD-G@@NpoDH1EsY?#fl8Hn5n*6zh8E(=_6Q)%^vot)f#s z@eK|!3hMmm03U8&$3!c@Zb6H;i36L@E?wip(Swj#v_5W;$^g^A<>0VpzNQ zrYn7_b_VRz+ga)q8RTeHb=~>})g1v4cu7`?ORD#Ws}r?ze7d8cd|&uy)1BPpsOz7b z3z<%gilNn&Ph^OCuA%jRQW5-Da}5Q|m5!C@Yq;qI0QnxOe|76GC<;=^cwGSw6IFD(FDQp+U;lICesbmuG30-~odRya?d5d$2}G{oG? zkGo)jN?Ws3$j4HCI*78-w$D8Sh^2RleAP zF$f+%eq7j~ZTIOJ0rr{1%^}vAV#>r2PP9GUcru%=h-;<+dEPkg&DlsBOhoXpXyz$S zNXaBD3@$Qqgp=-v4MW=l1&!h?zYmhB8AIX@oh2ldjL#w4$SFH9h8rdnkQ)ut%nY@t z0L)u&wy@pG^1V~$UdhulWn@FjyGlm09!U8Zdb)wgJZN#^H2f&Q?50utA<&`N zEHNYx+VqI6#c*f(YcX%JO8BmaniQqC9Hbt_!H<8-%^1u(omucHHKH&HmJ};ewJq48;`P!Vf?b`)b!Tm~`Xq*eLYYwFD zL#>lOHq!&E2S58Cyv1gePOG{Q>E|V0>%Ev}gyH5MAMxL)GB=M;vOEbc^F^4JIaNvg z0j$`|1azYcokJbaqv8s!#C5*fMKsvv4d>84Y*Y?#+qw-V1qPm}yo-58*=kG|`ASDq zl*O9kwOVabrBhq@6nWj0(`rS)-E3s%&Yx$$SeTX!Uxt>kn(0MaNie&!hRrUAvKPdv z6KT`In|w|d5BZo*V=|HFdPO+WVPKlOmwL%{r0%+9MPL+GqV%?VBPkEwG9mgba@g^R zR}LeALIK>pL1uTTlC-BmM+uv+%f5{D-|UhP*r3M;j&s|WwS^~%oaiW=)pRSg!$6Mv ztlJ=!YeVW%X7NT=8Rzt5+>=Ow+VlRH9sl7&62QERARlqdOLLl=?s8lVUsBC-ZH13R z9ECptT>LLMCi~3Web7>T#3{}6cMiiy8i%oSziPd$CBq*h9yD52Iv)U_Uo0Vkcgz96 zAsY>ukER1i4i~YA(;#wBTKER74%}}>f19o}x?ca(;Kcp?;}dQ2M9hu4-7aa0!C_C6 zGCS&+rf-nz6TYhBpfG;ycn)Gqt=k|QiQO#mMN)3+hpE!>7|PlY*TwE#4hrC0gHgxn z*Dm+djRhw+AzK}?Tfw;&yQPV^Fq#zBU!=R*SKhPJkOuVbvVW=GG57DKnQ!nQoxB)K zYxt_5q8rK*k{%34EUSZhdweiPp5p^Q^>Dr;4??Mb@9?_-1Wf93vG0_yAwbH5HpH=F zR*tSaYW7wp*!95`F&O#t9WAE2)1St${ky60JPwzm*6a>f$=f&ca4t=9t%6Pmw@LYN zANc{CHRkFxg526WsZXC$`3oyUcm!+0WtjPL#2V?du~JTg8$FWtf< zd%YiizOU%P&&;*zuiY;Fh<8W3sew&JRKkfr_Cq_fP$Z0>;p`FMR=4S4GB))b_YZcrr>7%5Nx)QqD@u?)u zDnWYRd0oN6gMFbu)>0cDmKjqrQR(vIDZ6=+d%E0m$_5kXgN6)}2zL z@Qt5cUhsy*xGbtmAT+OVUy9Q*>59#}`I@li?%9AjY&3s4pkZniE}!x4Mw|f_tI}q{ zQ0?KOPFTw7It9pOYdy$Vv5LwdFh?i>TJ#`sIhgIv!%CE#SrHQHEnI0ytU8+4_;kLTb6(ssPaHy4$o%ok1(w~GaB%asG z&BXZIG9ba_>%V+@P;nusz8N@B-?Ig(w8BO!_bS#_jmPcwb_MZz?nHkP7Uepd#Ze$O z>Qx(@kUX(xXqO?yPL9imBQ)t4|55%stW?^c4^C=V|K7W-=`e6$SHRm(Ax{}oGBiK@ zp1e29NR%=*_LJxDI+On0tHfu60>EVK1=&OBB19m+f3;b}$##Vqq0eskyX=SJ)2Ay; zSbGy1{4z`aDqeWDGmcelrvF-#%Va@Qq@g$7{5a(WQ}I=t?TGc>{~s>9d7$ zG8;i)cU(G&f4Q9F8Y>--DZvWwdMash*jDe)%XpZvL66mma2B0wIb)YvUi)HKANKDs zzE;>yAwTaA*UX#Qs9-;N!MR*Mhd_y`Q!(8|WpFyk2bA|QKthmt3w~-IkwvGP=bUEn zfmYdk=PBt<#!t%QdfB0YS+xa}`amd*V-RprYxq)W^~xm`K(YDu*dD&r(%$RLh`!Ld z^narJjtet>HxL9~bj^Q@xW6D~c*N~QlM1#UI#;@w*1$DS4M|sU*Y>@3OTBz>RROQf zEA`v=sGM;vN2>0BH;j#a)J7kc*ovgLQ-!)`g1fvzT!1*eRUSyEnR_^hXd9dF{;|=b z4M5rR#lfRD2nmIzODm11u|7b%#CS>{>3Sv;|AqA@@jyn02a|aU6)naeg_ z}`(hFsdtC08Gice(T&`VttAp#+VT%gR<9YKa?Bw`r%{+F4Q} z_XZ51tyZ{0$QNpLZ{+yzXN1z2Fr?Yn602+4ASrrml>nu=H~Upeuq$yeNQ@Og7N(5G z*>g)W9KxviKTip!RkA5)^QXa>FKk|xT&Uu5Saek1oX?fE_zP9|%-tJj|N3CAcDpmh z`}KC-!2c#xnu7zTK@t=D&QP+f@i1{R8`k*7Et{1akPkW>22C!q58_s{|=Ji||~9tu8V zJ8C9I_-0S+cxLac2EBKoYjxhDS{8=xua9!{2Y*6avf|~3ZQxk4Dk4>=!Ne}qHJ7R3 zhVhKZLLAqm)X271Ji0g)uOdvy-WGR}`?7vjRcl4Iv?R zc=$q0=xYr{->>9?fl725&64tl^m)&#r@H4Dv2J{B0n5w99ie}k6UwYgD7fO}n8fQS z(H~{onfH@G=dzOILM0BV!IUPHQmwk)do4_Z@|{8FJ38`LAz72ou5eF7Z8Z)`B?S~} zU$Hh=uXp*ZgFN}erk;{lUEeEyybX9ePT`QgGnd`6t7JfJQ$Z#@60g@doY}5KmqG&_ zKtrcPEK5!N-nMIWVDzNQ9trbTdNRLIpe#WI@72A8Uj9JbaH}yP`&&(el}QId@M0Jq zue_#Db2C?NV3?oc6)9<9>hGh)2&QR&!c>Xm@O+MO>ufbUo;V<$PW3|FFye4aO>053nrM! z+krzX?cka~KiMc`f?p>FA|UhCEt6Q-o!%9pnZ#gx)UZi~1MXROk*zP|Z_-f%T-VFf zxt^+_KIf(_KWW@|_<^ex6CNSP4Kaz5RCBG9z1&QE3j>2AWC zBSFE>qihIcB8rRI;uuSLK(AYnkbrOp$U;8-&^#9nGemCEttIYpwn;lskFMWg9ezMK zRq#EF!`uqzRWEua6`TEvyxK;OKG?syh@0BUx* zER9_vR}nAt=72CVAy1xA|J0}#Y448kwfRn@LXnfs?tE?GTOCY%d?|bv#LVO5S_CNC zFrV7gctJdXOdac5dB`mY!jDyQuSrBAm8rT9%?JCgID#=o|5bwX)N>rXAQ6EEvvbh($M}0Sc`e zVyX)TK~-H`92p8yowttJrv~DpE0AXTsbB04dzYH0V(H=HunbDsE-blKxL0*SD59W4 z2J+w*F#z)TWix$X+&*S+vsg|Q*qOspEHB{SMCL-A{>lh0zrN#<1brF?1Jd`=fQGTb zfjedP;=LJFt1PF9PVYY)%m$68^p@aads?p6KYi-RW082W_)Jdam&ND*!`fR$#g%PQ zqn(5V0>Pn>;0bQQ-9zv~3fJHk+?^!22DieU;O->2yK8WF_jjth@BO;J+xL$5#&~}i zX8@yWpL6zFd&!)02?@t5tY)HS)!OdL@|X-}DAmVSxDhpM{f+=;nrgWuOxx58rP3(R zQ#~!f9NOg3BV4^UdJAhf0tZTLO5F>fjy(y~u{}$JXG&Tgsh?ZVvdu!{vqYon$7>gJ zPt?@Q0YK437okFT8(H_4=O+qr>@w717jz0~o!HYf(3QrQ9z<8#<~_0en|)DH6rp>J zQ}`fZ&)YV~S^Ca65xH2bA zbHK8C-_>OnbYYe_=Z!vbe=*M-={CC)zCX?&&lV$$j1?sv4=Jzx8ChU7TNVe>2DW+E z9IOO2PoM|id_WNv^Aa6wqXu+M?Cg^JlrVfCh%Y}0{S!3#-!-|&V4%p+BT96hL-kaG z{C5c5Le5S8Cm05IRb3kro?HgWk3dy2+sdXj8~HVd<+u`f0JZ(L$cTNOK%7R9mX6jX$4#cmP5Dp*%Tu0sP@*ZmokxNmx^iG+QZ6ri2hl{}jczB7 z=Xw<$2@LfUYl07Fd95to(*?$tdF`1)ppcrrO2_S@iizreAz_F|6C^-F8>m&t%d3|> zda)<)-V5o8)MXg`n%Jg#C=bodIQ^wn`wt=*e*S!31U)u5_ILd%ZcoTZ~Vtz(-M52wvm#U zmjxYD*~LA#V8JOQL8;4mYk5e5z9NTiz{Ch5Jc*~VyW6D~P-VB&s2Q!l!dcrc2qt4&o zYCprT+!Gqn>+6BW`)Hjw->DIF1d8(1Sc-L@`%Yn|1n-fJ7m3)pEGW{Mm}*IFos!U& zpXf`a)&(e#2Mr^jBSO?(tKkw55@u?gUeDavc1$Uov3ku!a7`C^uoTGei&Px}BGLg| zchG5c843R|XqpWf^li!MtigWrYJb^20dojN*p}Ghw%%s^#eJW}ibGgxXsMJ+m5lVa zRV3i+$Gq|~$9f8dDxg*DP6^jmBGT3Q%4gy3@)1~?`!I&8;5s^o<0T9&m4 z-%X0ou`|q+>PKGQCTsJx!!ome^VtE*9kt!XH0l&|cjr2nwM4e|`Zb1i_BM$CjVd>T z`>#&5%k3X1_x?R_Si8|0K%b@qS$20DNZqCYeN&npILKX@%Vo0&+1cEOWt#NIcV^?d z7UIl~d1j0RpXkUn+MgLpVrR#O?kY6%em0Y*L!7SGkF!Qn69LLjfOwjjsWMP_Dm%(i z6B%jg#rB~h2dn;E@s?649?QVqHBJ4DCRQ}u3+_0{&a(*qFlrH8Q7*o53R=ic3MT*j zxmr~!3?j+zIJx4f=$>0i170zwyWyk}jvUZ#3}OfdAWWa|;2Fju14QzpIv_&l<|7)u zOLE{I`e1d>TRT*GP@&o@LxzOZU`7LzYBn7&+P3Y42H>195|YWRXUn%N3WtT$RfwRN zBVM%{8`}G7^AcC>hBo3g9;CPwpv*P?vM9rcWM!}JUE=9FjUFY#SbJ*$K#tiO+ad1V zl&c$j>T4+6-)_knY!qTsZymI|{!Pa-fmt#_x^K|K*LtH^!Q)HKOF{vS&9rJ=fDZEB zf&X5-d?dx#zaCi_s}03fU4_Mh=)emA(jpqChG%D{*oL+>*mNs667J4rCW|g(EkzB( zA}e*bjp>9!^Eo5t!u(PAqK^}i@jjy59t~AdWN$>@bsEkiw1~2R=;HL^fo^=C-ab0` z7#Z;F28PO>h39H@$xFGwh`U(QmoavYhM%WFq~7yK`$I1sRUy501}G}Y)Iov@FLMj3dIhn~J6=9> zbU***SlG0wsC+nen%>W)pKM+GOJgc!%xah3D;^bpOU$>oJutBt8k&B6Z^>Py6@!mN zS@^Ym9R;U1=7L5mUWYGr z+d6_(vZ#6%*=y^vG01{{I~iJ|y}1@tuE6k$9@vV~u6-d2X#EWW%ImZIC6z?c zqMT@k{lDG_pDg6is+4RKe3L$<>?*VBJR`IJXmo;+(Fkm5`P!@6L18wHC}td%z&htc z&n#$gESyTtIyVsCn=u=Q6$-DNiwC{Hh9@3#l@90XC-SfB2mO+rvRcH@w`nI^u&RASXgQiHggT#gJ(3C z-6sTPlxO+KO2$P5jy=68e2y};WUbh?Cgna^Yn~Kb1V%LqsI0s7SGHYgW!$O4^f<)IO?>u8$8?cce4Q)gedqkSGhz@ zg&j^S9imiBGp&aCA%SrieN7bM^>p3t$;7T%w|qAJ;3CDwyp5vPR}P9Iyp6H$RZOe&+~MOc-_OtJnOKGHR>yc7ErVeDPMrBEoi~s#=Z+k0Qcu1yQ#9l2S3!_ag4ui2OQ`P(L^{ZLD&MhgZ zN)|iW-%nc0aw`p{k&lHQ*%n-k32PM|lP4&npTWLn17WQjB&apZo>^(ix3#kVz49_u z1eTs{l&|vso;eC)z)E*7Niq@tm&dav1y)iw{4tHXSn8J&CO9VlKql4UXcD=5A!*3u zr?6W}UW91fUu~rt`1oFsWcTmO`RgD4dd-Lmdm1Ta?`1I}VE$B-RFOD1 z$IS64W2!?y()H}ub9#K+U`wqac`%npu$%{2Ea>T9pWEJB0H=}s;~U}sYTGDq0$-wV z^;^cZgb3_u0=e)vCqG5AT#9PcTzIFHg08?O$N?s4qFJs}qASp~p(l3mFNTJJ$9Km5 z+=Sd@z7-Q$)g0scdw(Np3hH4353%fJru+YVi1e_BIO&|m#vln>KyvOCWO6=V3Gws> zz_}S?sFY?YUN{b%otf_1_omNx2IY|XnCt`|xa$!Z^c{keDX#KGbsCPMH~f75+-v_n zoIbZx00)vD=7;OQKGy&H`jjSlMS&`9k)&>y3aV+>tlN%_-kxG!R7wHI6psv_%NL4}wo@TVf|4#YpPKBNk7mTVhCI-2Xy@#wGjQX~PC#%KC7$R%N#_*0cBk}y%J7*JHTQFwL-$(wBcYphAGBycAsV!+@5saT0JQt!Hz(|`aMWP=HROcW? zx4#bMD|K%O|NmLVNRak8$+TEDy3Hz{ucJ-M?dWC`vwYd*Xx=)dz43N857;gjF$Gf) z@7aoGbu(guUST8VHv z`F{GYe)wm1;oO73i)lo~#3Z}j6P0_mb&MJ0jw^aeMjX5vA6J+wF-Hhs*J3%$q|)Ic z6*yyhLrsQ*=vKTyEybhie9H{U)2HHmjs{&5J`msD?Ci7$ym5v<+jV0sh5Iy^e{)`M zfk6+sk$x;@E>$mUv%#VX_oEOa9jg;C&Sv$U8ARytpEnZ8G8`H4`;h^j2>a7Xk06&D zy5;9h8BW5S=RqjE^%lgRv#`K6eP&L_*`rD;5AY|yLyV?1F4$(JEmbhVf1r#Sz8Bpa zyJ17>KVhNjTx2P-1*^$|q1`R+#jz zZQ)#PUy;Z9+vEQ~e%YJEdcPBxSjYF2LD8}Q1w!tgw{Kwy!{;mz_&G>YWWUp3^ob7Z z8Hz7q@MyBsHvGR}Asis&W*J?|l&!&Kz*K&NtAyK`ybDNl*NzG!6cEXz7|JjnB1s!P z`RUn9`s-lh!=VZl?Q8e5E7N0Q77hq_HL5oIPsN9=j?>M``^yChbvJ+`s1Wu^ZR>cI zYWDo{;cd}eLdG1Q$g9|xo*NV&M$e*bm}hp8tL>NPC1 z`cI|-R-{3MhhJDGz^vr1xQff61CIH`^!H+#K7!ngsJ(H0OM2qX|3__tR3S8HD?V4R z$?JfOPmtyRc3Z!{4#6J|2w9Tn+;t7v6tc2TE02efKj)%GgS`gGQI?%9^lI-O88%LS z@uK@c;-8azeD-+_4UA2-E-?UaJYHq_J8<(f{XkQak4j-A82|b0R$_~uG+grHqpA}M zJh0tLK%c|EH;Wa0<&R6YoGjz$Xm1a1o3FD#o4BcE2J|$*TDKu-d6d+B*$nT*loMH* zmED;vcQm=J;aW>n%wxOGCntO%)x6<0O%n5b0B~;;$R{mckwVKuF>OD4)5G1}kB+@| zslYV~4GKy(=Qtbzl>4Z;Z}N4Cpm6^_&;Nc=3jVRc$7VPfolA=!)NSNshHV&wjKU~ROT01 z3fHOAI5OY6ep*OA|3Zl7Ecy~w=Gz&k5Wi@4loJzK#?t9uOZt59em)b2QDM=wo3v{~ z8jtjr(?FVPU%*=NV0Cli&99wSYK_#cEL<+&26JEi^#<7hT`z;I#?J|>hQXkWt5VvU zZP%*>=PB!_|K(8r!Sqz^jz#X{g>-8S%>hrN!b)9flKr`>k8MDa-){uDtnW&ZfH?MT zoHdOGztaxui0o9M$6SU<-#kyf9rgadxfe5ka4#|)Hm!kj)6ovPHF^36y%B$yLF6>r z0x!U$p9ET-98(8ymJ{A&3ZO1Wr@)5^l90mn2j*BJ_)|X++at8ap%v7^|MOBmz4@_x zcg0xw908tY4DL9ZI$~NMAvdJ%7ahVDY<)qm<;kg@dB_PA74t1I1Y|V{i0%w#M}Pf8 zQ^HEi?hgSDFrL@yG>?{gZye9Ja)Tae)AM9YZDHK z)oi{$dFUT}rUPI+^vbyL`|*4Q`5plOsDCh2G-soq3^cb#mIPfaQmq$sFG>Cs2LKc< zzh2zgeH%<^$%@V1;@BX#eS(y+a&g4Ic_aCGA7PSn zYPPTPD?o>vZjvDSw|(3$8@zxHf_O>O1wJ z<74v)j9(sM>L>Wx0#Hr%0lhYK%LmUSYbZBag=V9>Qa`r7Zei2C+z#vPiz{OcuQ`(M zjrM?QKl;?HEy6TVPL-o!!a1C8kTo9i#lx1ksFv-!hN|XTE(L1fRAQHw<_Rupo{XpK zQvydf?s+*gYUSwyhrTP2q{iF7ox2%B3BnqRXzF2vd{8qsI;PXAcTHvpa@Ym?SX@-i zFA4+g4eypzXFuZMKQst#&(!X|Ma?q+T#SoYi*YMMXkpPYbZBx@UL!oy+^@m81cYO* z4Jy0fcP5!EW7!;cN@MYbsCDr00xA^k`W`C%yM0rZ!wD5Q1I^|Q)m9%;m2baU`YdHk zYU#IqGP^BWP{E}%7?9YiNGnQRdTj;jr<_Wh((wXtcMXC#jM@=)Xsy)r)Tg(A%@}{B zCHaW-#BTX|P!qwwFPi?hi2q_K(D&X8xhIg+ox$$V(pJexc4*OJb1ed*2hM%vu>(IAv}jC#X{;LfjSi`r){`?A37 zjhOB?GWc3j8Txu#US4-H6*xtuYTq(w1qqJekNNp#A^Cc~GK?^Qgekx!v%l^=cboz#HaPgyji0<*j zlGE6H2QBhupt9i4<+%f-2<4U(LMpN9-?Si%TFoDT8E$M?a@53^YOL7I1_TZ!69N3# z_qxZ4k+sC;&5M%e9Gi4O*-MP1*A@K(`Eh9ALHt*CZWn*fm|RcZg+=R!ooZ~Ni3Fcyo&Jetc*UyRP! zRFW6#Kn4*G-8X;XE|l0VfXJT-(*V$3P4)pczDsXm$H~kn_T7>YQ2sWN6UT8GFYY2q z-jYdx&-EED{kM1njk0|ghy5O#4}+R2$K!Y5g*BX~MW35^Bz|VOp0p&xBm_45+r&B# zwK#NL=IUf11t&jw)+pK|XYKbpWngnd_U}2ANphQvkoN%nGR%Obv^l1pnT0|hOH=D8_k;u>rze3RvD6-hJ%apDpk!@KW53`_+B}! z-wagl9^TK6+3F;i2R3cIJyVmz@AvZCnywFYXQbV9?$ebKlZHy<`+PIoi5EJ7HO984 z11n}fO+SVMF@8KE$v5k#+l{<=?<1yLu+r(*r??7?ElNF+#NBLsbut7Wc9r2K9W4WgqHt zkjY_?wb(X~ePd$|yYe0t@g*?&L6#Dnnmx-_(0ko&`SEvRe?O%tUFpYd_1Uup_Wk{x z=1eFib8fwZ2^glDliV5YXDy(gp-<@d;pMW=if7K*7&PE#o+oH{Z6I0XOu&)6$^G09 zEiN>a@{3(3C)Ga3f~V#>-;FeL)69E^2+1lO~0<>EDT%GqobXsD2aJe$Qno@ggl<7lHYt8{5 zsIWw|Pc`8stmeq2pPLV;H)P=^EPc0Y(G+QVaO)D{%qG;m=l6lQ5^+9#;)a05NI>;) zSAg1sG7`-P7x7<^)==3W;=I_`Bta-dEMd$mcc5C&K%jW`fSc~iAV^Z@3kgXx5oI(( zD|Gr%M|>PI84_;oa*P$6^M{O6XWgQ8>K_^UEb)iHLz&+33OlI+1}FokN5#3ZdbLs1 z%`#MA>jYlM5pRXnwpDtz=`d}+=3MNWq>nWFgG`F3Sa_<8 zK;vQzyN~n_UU_u+>rsGL9{8LT`jpu?10a@T1ha7t{7EYZe1GY?-~|JBmK$+O9LLyM zr(dP`D7*Wk9eGYQ>gXXMu;%Cjf=DMFl-vyuYjKRVjD?Yz&s2S=E^JPpww^H3>|Ui7+Vz+Da3Ad)l;@@ z?uQB>fcVVXHiJjA+-3qhF;7g9CZ`(rZ}&Z|!x9w}ePO`)!j-OvZb)=A`;)Xsb+25h zc2}zmEl7UfyCrckGOQXTWi!$~*lFOLXjnG{=%(BT7mMZ5C zm9yPLFNx21EyO25!{4>$*x#T;bb=)SzV4(iaaHHfi1Ip**v`Nb4UJU`7a~F}mfkpO zhAC}J)L;ah-&GJ&qV@PZIh5{8zt|`0Ez9CBU&W(!Q9Cjb@YUkLIuGuPYMeK@*0FlJ zG?XA5n2f;5dZ>fM_M%r716?-r6owlt!1~t1@r*v6{R3he7w=)=^FGs|hp%Mq=FzXb zG?aZx)&%-WSFu>u;FhD?KVCgsjwS^G`QUfBrxk&6Ke(8;Pe^%FQQ%SjIVC2ULBRFh zM{00SYAx;zAB_AID%}|bsIYyw%Hv)~vwpseHC7-;$){vhjM~vT2m!R+G}%89`)``# zLghDEJ3OvUJD$x}ZSx=2+pnN59X3G{X`&h5XnGxVqt<^w)^0KhvIi|68TU=O>qmDS z5yZ9VJs41Hsy_}~R@$F;I$tkRYkcVqMD6oONlkm)pUn&^nC8=;X_SCLjS(6BLX(Jw z_Gy#~rc7Fo9uxu67-@T8WGkg(rNSs6&63}p^nA^KYq51?=WHgpm)zkt)><=xGb5GG zt+n!bdzgfzLi>wBgb62AcPe6Lqk?p~PCO5#*-w>*ezZ8!7{u{>RCgN|%Rg8&>pvxI zSO9!?v)Ln-o^t)8cU+X5;YF7fuf?-3d;9dnLQ;ji9__@7glW@$MbYzILi$vgkKGga zJrQignLzi}octCF{0FCX$QquJWGMR%i&H^Zej$mH1l3@P!C!k@&~nkd^s_PX_K#Bc zbQF64A${+n)5>d-Z)y-1-Z!_d2;eIN`@7h3o%b?oipVkd+O;qIY(1zXMrtQ^DtTF0 zy4$oC&G%p=K$y_tUta&$MVZFbsM7J2Y_k!4S*gv>GbZNO{y*<7KhCqH&-b-bIa=J_ zrhaWVkBJOwQu@~EHKIaPtlAJb`Dwa9Q=x`{$Z|4^G3&R`g)&#p;szy~#1;b! zUNLI^!(;|}`a~{eMBkdbQmuf{RiFId-F+9{(3d)9m}G>X*Lt(<&Kpf68LhYaCBJZ5 zsr#Rxm8Sm2a~4aVBIdLjnsm8!c0JuPoZjI5?4Qe4zkN0!l_Qb$ez!RMx@wQlS(|NK zJoZDKcsAhUy9Lwmie+F@gwpwvaxZSuJU>m7&{tXEydPam)l0^#+>Eko`Q%_#F!7T| z8`JX-QOnnd?vngWdUCQf0MhL0muK{gaFnV#S~{PZTD@3%Z%*s2T7)MY9UE@6Fr4$< z=9f`TpW%9su6XL;Pe#(XG<_5Gb63U}+1Yj!y{8-RH6--``6wwLPo5IL9}0uC#Er?M zQ0B|BxLw=I5=plKi|_77VXwwIrUqV8Ug0BUA+8OPC+DT`d-UcSl%IQo@aPh`Qrt&# zGhv~v$OW~b$#X0or$-Te(P^>F@ls3V36E^g-~;Y5=!s40;Vyu$0d(*}Rp5r|tds{x zfu<%Y>kJ7TrOg3Xn5|l!p=Qx zum_hpui?w1s4o!EtAs&^Y;X#a6WIaclYPSV=y@7D`m!mg4=++m@A=y z15hT?`-#G%TW0b@bLE}gYnA?pL1uy!K+Jc}U^4vuR_!l_qV)%F^r)@j2u>VqJHvz& zryKx1fR$d}kmMLanh|o6z+*gguM;pFL_ieWw#{aVSqRFI zaTI2jBw#NDMqr*Od}p|~C%%dazXeF!IWOql;<4dN8&DedV5DsJ-Bv@6O2(pRKS0q3t#rhSt~)eXsSk-ccfOIGaI#YMBoinae)e z4iXIDCqyCmKYfFLvs!=)?>@A5h%=X$4;5IaX9<0kOkDU+0pO3ESTc2(_xG2%xM}j! z{QSf~!|1;8<_Y1#^*7WgUTi=QVB*WDe5W^OcEM;sG)9W7X~{ObQ}G^5u?A3C2KOeg zyuQA@j!d%NAIb4S$h9>XB5S2a{>JcLQf2~2t!74{C*ZvkQV*sXLo!>vjY_S%+=Q*dHg}iK>Pq)uzjF?Nu)d>_HERsSmx{#65 zS45YxO8chrAl8LEaq|3jn|pOF4r4dtX@ivId9y-<6T?2Sa9FdhRVchg4HvmU34fw* zga6<=-oso1k0IPTVm~-X4^@ndM<1+)X=aVne?FFSGvna9pu%aY)d_Pxa({I``J(a7 z*ho3uRhxgJx+lyhXjQUOgwcBQMM_BPk4y(mbc8vRf@lW@p3Ru>crAL>(AiMQplEdm zc5rPnYJ+x8eqa;DBKO!pIAzo`E`m&>a8)?gn=d#H}MWhe?ffkjMm>h)oa#BdcrG zs##peUJMMdx#9BI4UF(3Sz$Afv~FL-4G{C0vftNj>Qrbgy9|PDp(Ie|;6UKV&QOAC1lx6cu0XV_HJQ|JH*33(&DxA}z!^`Q#|zw$p=Qk3y@-@>-+9{G#2Z z=!s*m_uaa(^5Gp z0M-w!z2g>2wy5Rtj0IZ5T(eVG^i!$wy-QBN5RhN9$aszTy>UAzQES3*Fr8A2#UE#g zKUfS#E;9uS>Z*I_0SZu_@sp<8(=&FxiE>}(^uX;OhoF;w2(O8`b^O3<<;hGanqk=n zGMwuno~i{pZZi47$+2H%FJgC}=%oIOTg;9GaEslLs4u6>9XX>al<8WLnK@AHL#tNP z1e-NmLvz_<806n$i%{28l-e(sR{;@zLkwhXJHd~gF13hguD${Y`zrxKBIbs=>y41| zeLPA#3m_VzSWiq3DBN>^(x~KcA|EbkIWjp9<8o8C4$da^!!`ruWW`6>$4W9ZBW9 z-u_56-t~^=;NL)~Co0%r8!=ele}52<%J}oR-@V?G34uAHkwCuiS0y~I<#Kg^biJ9n z$WN>|kL@$aEbk=)(Cl}tEHJ1`ZJQrRW)3P3l;eZ~Bfl8t2`6soKzqMSxVW@ zSLmoQydpsXNy1F;<`s;woTL=E-@2VuLwjl699mlnX+ zphh+Cf01e#BDV3X0Tyt3{lKB0DDC9;Yr6gUz*_UDkE0{MZfh0o=DGE5eJ+=Z zGG-VJyp_IHli}>KG0_DBu=7`b0%%}7y`K_j@7{g`SXMExK-psGvmF-Uz2YBz$A9!A z#UilIUkq)8%2Zkt{18CUW~#R<83mNfN=8jGOI}j^>TRXYhA4eu9)>?3qwznkCTKm} zIxyibJ1lEsP~2ls$_t^yrarX9jg-y5t3eJoxivc_7K(8%Zd^y*jQY^&_d~7T^-a;^ zhFjYP%I;iDZyfD*sdaCqH45rIa9xV%q)z5ROqJnF${B7cFRkdJF{`prN? z{!@Z6=6*sIm~zqfB3OIUkA@{~Y3r^(u=SiqHtm&1dj?Ww*1d@Z4L2a9y>6ltI-AWm z1uMj8U-OX~xq<=l6-;8cJbvKlgat$LgM;zE z$165=K(w-BMb%1PjiHOlRfVHhM=Q+Q)q}T6f>eH_|A$M-<6q3)UiFvz>|U3>3u>^M zD3*`muC|nxmraoyyJ*@zUZt#egN`%+U^R!$n zcP{+YQCR!v4LSCrzGdg~{A{;!lb%W>?bA|CEJxKsU z87@qNxJfIFdkw>8uORC<77R|@ua?IOC(ti>O)Xr0o3FpnG=m!; zc^~OJ(knX_fpil|R;dQ1Cb$-5_uJQXib z*9la5liOqlE<6uK;z|UR#{&L2==lwyUFNCpsUgKw7IU7i94R)V#?38Thf51 z_aX*L8g8_<(t0Ar)J4>W+&q#|GqKETrI~atd)?H5G1U$4j$a$!Zy&v|a&_deU5R=} z*zx&Q^{aM9{2A_Ht0LE7|8U~A{ve&g5)IzaNW?x#d7Fu@eHKIUT*M4%_&mAEkA`}C zYh3nwYc6W*Nw(ysKZw&O6CZF{V=d?Fjb{~QPWxOs$f1!vF5f}Y6z1mUvrUf(1L~y; z)QEN#GgVy*V2X zlZ??dbBC$ip&ZlbmFekrcZ)G=wjUMCttf)!pjNGs+)^V%_! za6!l|Ikw55!hA%wh}% zt%@4PZZFf9p6@wET{AU&uyD;Af|G&ZIoMmH(G;ScoU=V>zdCH}c)t|8hbb#}h(|nf zkGo*}LW4tt$tuI3a<`<7)oqyukK+!*OnxMD{evSS3k+8+w^?%iB@8XPB(wfk91 zE-b!7N62j@5qg2Jh0&eBMxQMkr%CU1)*nU0_p-|JyAj#b|AK}_+e9$viB2px5YHdn zy_RCEzkN5}?VXqqKn9?wGX^>pGQlLx2)WVNo=Wq9y%YB7nxd^Za@6PgJFc6N0?E_Y z5tgZ!f%y#jO~lo#%z7VDxxxpV{CeVwbD|24+b$Kle_lsmgJbB_zulaFV)SKImydP~ z>0OG+)Yh8qrYrI#bqxIpl8B^~`Y@!^n4$f3p)DY@R^Cuotst(Hu45&qrvhh8i_}}; ziyn3Gm`Im&W(7_Ojp&XN?YtiP^I+;|x$o+tJL5Tl(kMi1zJof=Iaock8aMHr%n zt$6a-;Es0nPzs~SUvbA$a8e-lrpKet-1kiVg}cI{yyyf3bcI;Rkr}TKdLu9BzP)Bs zRZYS;z@8~r0vcJ{Kn}bk-HIuf7xRA24l!5ni=F)tmWI4OEjvLaXwsnbQQ++TE6qt&4$sjdIy#zSTGN#QG}Wx*lpr8@a+fn`eCArQLM~6IdgmPy&<3WiNn0pKbP- zdYaJo_5G!`$zmQ6TcM7~pwmKXgGt9M{my)hc$AJgW~u|ZAZPYB1UF!`2p#5{w>Qye zVHXw%vr3x|g?7We^xdm_p%U)@ipX3+_zbuSxrY+wyb71ieGwH)+(pvw&ja5AsV3Qs z2_1_o+)9$Qq?$!+BZ^{SBog++WXj%NVay(G(+fDbv30Mz?bh`0vn{_eo?Ki)s#xyT zxYpNk18+4nH0m8T`DGI80#bP0sEwoeweW8ql9~tE zjRFWr@1?eTtCG%+qZkdvQ)4Bus0HtYqv^30+>YV_L1o?dWX551th&wu*BNnL+D$e| zr9Spj>LR*Glmf~Re(J0RQek0X3Y-(7sMfS#>tt24hszauLFJFXjm~>&+_AbhJomk9B_D z)#clKRaba*tTb4t5P+_kSP-E^_HQK-7?F3ft&R^|XyrD6-y_uWu>YERHpyoK}IriQ4YD-W4{T=m95 zMS?PfJOR1FUWD8qzO#RdQg(HB7sxke(2_0} z1<_+3ZzYBRLpJsyQ4h^rdaLTlcj`%2j}cEkfBY` z&|Uwr$U5$n<$QX!IeVYSOGHErhF29qZpCl!HE+f01T>uXbD#H3_SN@_RnaWT)u7ND zoNNVF2qRq9l>X+dQ-3ENF%Qs<$`A#3oR?h2L+|b9V zxJ-yh>M+)aN93P8o9s(sM|84#OyOo6T+sk}0;o#ZOZwQf(dou|gLh51*vnWI#!r|V z7!8(pCbi(#?uTOWJ5)GgXykpkt3SOr^?hC`SawM0h7z$#eS5~0%EkM^{_wfuWzEMM z6Y2*;{-o-|7zX>ppj-cz+`c$!Av#=-<53ajH6~K19^%F`AIL@>2@YI8I+fa69MPWyN{X%ZUHY={q5C244OkM>K{|uI=Io8Z$1aj0MsIwxl;_Q^+9+xAGmZ zrNYpwn!P(iO)96C@V)ZWVUZLy@iM z&F3!MGtn`PM)!G84G8z) z8zlA85gUXRo;IXQMz*V246UN#GD%dGW{t0!E0rA(wF;xP#Iqys&6X}51+wbTe=|YK ztv%c-p0M}K* zp=WxN!8!$bzV;goIVx-cpg__U$gsHurREn?-(~KtzYUKU2gK5&dyasE;OG1m-A8>X zX2~mA)d#${g<03|xB@@)^oXOGS{oA8y_W0E*$=)6Gi7VpPo|&gZda*B+_4%R5t}(w ztG30Ow<3)s6CXMNyx}c>N$h?UyUT|+>UEY#$i%~1v0e?~Yf%?m*mk|P1;N$y= zwyl1%RJRg7-u3HbGP*3AExz`J?w$1hc7K3`f`#%FK~<8(r#GfwhJDg+=?NgJ|0ZS3 zPhhmPl5+gn-(j8qo~m+H4)^I(VZLUd=1!W>Pg47pJ^BdtJB@Jlcm!Ca@#T42~4-v&}G%R=s}GV_UxNJn5ui z^TXo}dW$6wZ}!70sgsfFPfGFY0u02MV6BCAHSPk0O0$F=l6N=-0*{Yg5!wbP-cC7pv2MAF1w zx)hlxA0=K`nB8%I+SIWbR)k{O)~7;rc3B3gG`nYi6YMd|OG@cljV`xUZ2=#EfS+`7wJ5&prrNYp(d7}vO8pI^(!kX zvT<4m=A?8T`V`O5w239%+}wEdg0+CyTTyGkcZ#7nMl{aOeh^C|Kq84fX6@l*B1t^i z&^MFWli4PR&*v(1!J~}iT`?Sp{#N0~-y%4Ow&bJi7h&i=HtzJ7Ef<{xhJm(AX7qN$;@3 zZrL#Ff(u%l9mDiM6gtR43HTwT6J{gpsFi%4OQQCxA)4hP0n%t8&KNXDbn`1_fP8bq z3}r(6=T4YA5x_jct8HD0{`_2zBuxt7RA{kM{o#hhe}l7K@0J8~=p$%tQklcUQaF=- zQ}!i?l?Vja0EQ&Ci;+o$DsT~_>O{2rkD}k=xWM-_@4fMXyZ{A!_GSQxCH5m}f1vhB zo}l5f)KX&8DhR43o@TLY!7F_H`0=SKcI@vp^9Yzu_*z1`s2(|t$hP28`1+qf*d)3z zI$>!za2u*5FP_!|-#04y_37_T=FiTOX&16F0SjMa>68Nyar$Xhm~1+QZ*I5po$JY@ zfB(D;S%4k2#qu6C&=)}Pjm&HVV`oO2ZczsubuBDJ$)&Ke5!WTVPufi=f^c*bT!%lO4Df;4?ZpeCr)7g#b;0%;wHG=JuiC(Z$7-2d>hHE90^`CnCA89|%J~*w{&l?WzoBt@UV4 z+yTy0al6ZJJX;^hY$_+>POZr9@~8b>J69E0FR;DlKJ4&TVQ|EkhMOW?i+|C+XNa*od0XgpBvpNH z-Xp~ov*71gr{7iD_FyG+ozkk`P-dPi%*8@J9NQ&geEIs-boZjgq&?oW(qWaXH-$cm zZno_7H)J9viTn%ElH^%7a(H+*Zs#jFtvc8H;7~%9%Q*t?O4Cuf)g`l@48!m2QE^i| zb`#C2+WUzDaws;AmWy?=ZWj+0$F=*BIT=q-O!LAIrDpGzi#qn@(EnpAl6d~U4`u&p zAO7o*7k>w%!oAZ7^mTW9T1E@SXDh^+C{~*=i6J7g>RAn?%$7|Ji>#xN?*pgQ3li49 zk{e6qVIJ`yxtCkl&>Ml2F;BF;s&QU~qxmL)x<6f&+8L6X9b3UxFxFKa5mTDjVQxC! z_`1pUULB#W@VyS{*}c=c$=a~?+UBlU(d=b9rEF@xMT^1OXhM^1E&bfZ$k7O-rF+=M zrDfnroV;E7SLfS6g7u#a>n(r^+3WqHmHHV{yP#kk zU?cS=a(`)%0k-tg5BTj%BigcWLtv5%AIOpiB+N(}FQ?dg`vkf2V-NSu*>+T7mjh5S ze}8dN&1^LZHPkH>$dA$I!~kYrSk=PJHx~KSkg3FUwvgOaNuTXe3MqHx#xrRycBKFR)k0P!d*h<#A;$E4Utqd*_KO=goaDo`vVFzksIvOdom zYc)KF@ex9#74tsw_r`JLmfoHFp%8PC?v~Parg~n572i2Y)qc5CsWrIEt9G!liMZRPv1WV+F+u_p~tfS@ZDVCzj zJe(yC-r&Cv(xoB&Pce0p#oz5->Hodmzk&9qGNKk~F=-Vaw3yfA*#uiI7u%1_zf|aN zk1?CH5()RE5`S4;?U+%57k&GEPcqiE>HneZt;3?;zBkY#q7q7nNF$v}cXz|kDJ6}7 zgn)o3Ac9DDN$1d=(%s$C4blyFk0(6w{oUtY{~CQp_{^TQSG?<8?{Z(eb#`_Jl&U{^7j9COp;|9b9rH9D>t%gkZwY}eZ*NnJ zLO8NCw}?s)`_qB0hO4z>DrZ1}2FX}XYr#mWjHRYpnQO_j#n{H{8s!fcqv?e*(_Rgp zB)y3>pCh4DX}yvAH+4M2Oakgu+S7{NI@0%WCJ3%BIgd9vq&riPGo576V`Gy)jbu%0 zITAkB%#XEc5;d}K(0o(sI_UsDs(Q3tt#?+o`BtL%+6OuYeO4cToK7ZhyFIg&DS@A^ z-_+D}#ADWXXPMvbOAnn=EdoP~z+9G&)Y2}kmkSG!83B_EUd5fhszRAA+H8Jo<-`IOGOS6+JY>V3|zpyeksAPx_ zh$C9ji^UD^1(Yi)6gu?dAHn~Ji{|AG)QD)B`Gq-6UU)qQ@bWubY1~Ih{~%eCG=i_3 zw$X|L=U@`C<%?~XyNW=L9yCQKfs7jIRd#;9ln+~GPjOdZoO6f1pL%$qQ!D*b^uU4w zCNAzL-emL+=k*pMqxZh5RbpUaiL$e^7w3Ne{^H=|Mlg5GI(@9yGa`lBP3vm0+dGtUU{KOngT>a=4ER1&@tYI%F=+N|O1>0{`d`oH8|UO`Kj|-ugszPd%Of81qsPFN%+>3)Fg|F6z9FUY)7mGti7T#G)y~$hY!Qm z%1hyLVqQlLr%+hZDD3K#S}l-26`%1vJ?M?A5Uw5f4{I{iQl6?Y;;Q20>1IV9y=r}1 zdfB>IcR!H3%l>A7$CG|)sl>>{gkr1sUY@@IYh()DidB)K0%BKHEVB`*^-QfyXQaB@ zV0%Af=LYJ>k?=0sqqmlHyeNoYv+G}(wP!kG!QY9%d9?U%M~%h=a#E8Cc7)7_^k{eQ z=z@G>BSq-WKQdtQ$KYQnqMROadcj;vmESG9)d5x+Pa;p%!A@kECvW z*H<=N%{7!tL45b=<t(^>coR$VAH2raGOe2^&CIgeiF5BA79 zfZ7J(%C>f?s27js0(o^)IZPMPH%ODO4*ddJ=n0k!Kdl%F3ZJPq*leV9b= zqc)SZc&b0VZ1*9pBVY}ZJj66q7VkulBYV8g_x^^@PW($4X&wG`l}CMPMr%rZ@%&iG zc~^Tni<^l&S859$0`Ptv10Tq^qM!;i-wDn+8%8wH|QldQKAM_sGMhtGbah;sN@KS zwWCIEI3+wlI1R!bVg9l`TmOD2Q-SUfhe2Bb8MlA}XLrB8aDqlIHoREY4MnzpeuZwl zb&`PG$6cq~yjMp$fqO*Ec<+u;;0zqmM4kA_`SJ#Gj#5SX_VkQ|{6e{1gQb&Z>GO`b z-TN01u)O)n=4EKt1R{qwyOgo!WQFs(s^Zrw#^4A4f%njYfM|iq1Q;Ry|Nceq%$3K3 z4Zc*+n{aA%mabgh!#(-piVE!f$;4c>cclqC(^dX6L+O!`KK_v?jt!aE`EL-mgd%eB zyqTxU^|d(5JVvd$;;1s?g}a^VHkw*}TDZ9rJ5Jr15p;1Pb@vzEf zg&xGe5g+|JeVzeOhM0t?ak5#Bs7zJ6Ck=_}-Og1cDrd;zh?Cc^txO%ZM&wR_$g~qCy9~J5`_p6M&%m!nr6$-Jj z`S;I;?B-ENSk0xMYy?MEpYvuW5YH=No2pCG=@8|g{}?l~BL9cu`&qXwQh_4w;pP2> z|4}mjfnRySi-4jEz9d4U!;}h*rz!ioMh!Amnw_>)Vn06`I7AD{loxw&nipyP@pH8B zwDS#iMp8KxUuj&P!PsL~e@Z4-S!=<6%qPKoiHRXznO;M;#^d7?wmn1c1j)?VnV~_T z8J?{%2v=-9Eq}eyGZ|nZ;M$a(Wv|M^j```+wR{9K1>sbM_(}bX!6&hNo?_7<69*$V zv&l9Ou`Aqkr0C*lRqM+`awQmvxeII1GJKG>EJm#5z;Mvgwk?CaIm5?@^NjpbRtkJ` zel^AzaUj2oy>w4G`yd%kXy=^g?J~S0u3A%RJi#^Y)?mya{S5b6HW9I@>(Wk?~=wZ#e0m@lrepnb7!kPVgDMs!611oeK8&k*H zN7g<=tS(nCG-s6;R8*>HUo$%ge!9+AEujr08k2FF&z>gX_E?OsN?vcuo*kMRYoO{= zUb9KIyd`ML)`Un5alB{xSbul6w@)n2<;UI<*5k)72G3#gkSKQlO~U+Oew_qxy#I?z z{U0udMq8VQ`>D6^wmc&zXLM}oHmMK4Yd4>h84HEnCYd*tmrBlChCxbikV1;z5@;~R z(QB0YzU44C^ncw`6`q#P#vk&(n#Cp9$K z=8pM;a?e|{YtaQW%lK0h^X*7Xz)1aT&$7PkRPD~e!4Q6rD@lP-bGf~_I!HI?P_7$K z4g)?G+{Tg`MF_s5zMY(TnNF`Fye?Kn9K! z1_jBquPU33rJhoA+aJL*#IqzyN70gb?$PO$SJP%KveTDNl~~`as!9*h1)E426X!&( zc$Siz+p|rRLb{$EJi*QvCybVAY4J$9GR5cO;zFw-$y3Ws?~=!}**n1w=XckNSw1{E zntY8Hlw+G?-+I7m(u6iCaof0L)?eSu+(&FWrf-OxItU^YFv&1LY5oV`&ES| zY`F1hyJ~V2#hQWwXPM`?g>)P{wo9#xOhm2^(qed#?Y+NwtlzCvvi7In;`^uHBJo0} z)_TU|xHltRD|jYqMA@GVIL;ETKAk#YirI_FOw@gPmBM;^a}w34tiPP0*92HSr(&hp z9=#tXxQ&>Z2etNY&3ZHxidAA{sSG&c>31Il4MA&8v%SxBmn)Jib5`o>>s6pmGOm|n zGGeo35E+Z<>SWo5{iBrqu+^b?33h-5DogM>87 z@_Z*si?wiX&;D8>C?P}OB9PLPRJc2di6fBU;(>bvT^#)qDg;cmdRByesN-|LI{Gx_ zL?HNT?#)oQdU6AYxKA#3usk{C$>k|ZZ?^k%n?3y8o(r=2VEN8vw!;S5NWG(x>Kmu) zHbRSuya4X7ETl|k(>_U_TOIn#D#uHH$BpMIZP@ab>OB~FuRCjHq6&sfHD%Uo&t_ww zPJ#HNQT2@K!ja}as)OkAP;IUNus4aY;PhRQ!{*UFJCyh9VG|<%qb6*D;o{A6fQ0w= znh@i$SMqhW;$*1UO3s+goD`i8-G>g}+N#$B;m_}yl+20DY{b6#@$ zOHDGJ1%LY3$s(5!EM^*aK9`I4Aa9`wdk&TxFT z9F5Dw*Fsbl+Qgi$tl|Ti0SiRrmT7OYBoVi@iZx<3nL7;&XSJhp)bcqY-ATjr2v^xo zbfesn;BxdYhO1GxyT6_&woWR{U}t0XQuwcf7nOpYX!(Cn)PFu4Oeig4)zm)bv_P7z zKO`BV^R}fO>KOPl0t%9RftBB1tl%@yQ4w15ZT;^|PE9HG?^>^~aSKRsSZSHjM;o4+ zrOujunzz0x6%*fAYIao$Orp&uBTWy6Vt{f=xpKHB!&vEHxpTea=9sxxEiIBBIWQNS zH3Swcg|4e}n#gg;u$i<)#yCBe73}Q*lbCg+R!*Tzb&Nj7{BqTYna_Uv;Z&_jg;rZg zq#VBAaXG-;Jgj72@$vY^G8zIOZ2Ptwtbwm8YC5=VwOHWxprZzSsNX;s`z4PAg_I? zgc|^^Jv30f#$}7BY-c2>wk{A#!KC}_lRqw=Dv#MtLxVr z!r$;Uuy5o!P!%-aKSn#eRwCTG`9P&|8Z~fetb&gCKfY9u_0u$~{rgKL@k*1@O^drP z(D05Liqe!#Z6NaW&;RVjDF`YB&O*d!VO~mZR zz>YNh`7Zooq%AGVZ;e?0t;AseLZS!OIdm23{^MtcQ6m)__X%#a=zG@Ro_(_MYv@}3 zzPKprV*fI;Cx+RtLD^lRVs|bRTyh7OG|64*s$$cS6;q}gsv8V$(MrX_OhcaoNO@k& zdg)p2YRxI}NAv!%qyt3ETN`s_B|*WjXO;%2)%MpJeh+-I;~1@JyF#DW^)z^F@w=a$ zJ(nf47Vk{C=M&>W*6%#$a60!?Gc92)J(r_Xzq0ZfZk}lKTNiuf)rU4iy^T;e)b)kt zOe|_0MNx6i2%Spwj!RV(=_qzKmxfzGRAPZtiAB%dcAf7H?-|vo3+SsIPw&(n-jK{# zXncD9TFI+ce=sbwf8%Tec{P>{_1B4$0p^8@x1Zu2_#4pQqanEWr@{R*M*H>u4A4aX z2+-vI@8_r4(w_*tRjAmV$t1elrucz-G)pz?LfF9=nlC_39xWeVC0c@$hqg;NmwPU~ zJY688e49vUQfw2}cNDO`Q~^L#9(Z7Jw%9utTZQy%sM`4Wn*-J8s!)x@?VatBw_gf1 z%&wDo%(wFIil0}#Hc2(#{a`W|sv2iDTwW)>G6f%yNEt*#Qi8*%ub}O;8)dh{3kU}? zfMZhDYh3$g;c6sV^(pBSWNdR9Y~J6=LZN|WY;t%kiZ4%z7UcmaOf@GxCRG{ZEAh7QHFi%v@I6tS&p#bsw;b?G}@kOwDFS&Y6jc ze}QUfngA*ji+sFEr3b^z=IYwJ{-*#S1{^F&twH!l*8V9)kS_k>bi{=;_Chm%dTW;F z;z#30W)lYpSK)f*eBu7`ew$41lAl^It7D(?=44d_{psQ1Q(}F6tuhauojFfY>)~8W z{G2OmF1G8Va~YT`#zCPm*j;2RHQFPkpeZ=a_)K0NeEfY7vN>ZJtrWwaocD==hiz{_ z8{5r%&0L=IcxOC4OQE8?sVgjS`^$7mdUl>Bvn2ZC0@K~lvXNAXjHV4(j5(Hb;q*#k zK7xs1tMC|@B@rYiytCyZw3GKnqt1W_I#;PH_ga)LUaQcGknOyzd>I3r zj4n!5O0-OJAa%vdcAGOMPoCTOqT@qJK8f*170jljrvw2vudJx(r!>0?N{i+THe({~ z_fu})5%@eVgf%J+Pv3B@uT2RkRVj-FZ0xa$X2Gzyf@qy92R!>{On9>ODnTqKP4#XZTfVh zaw`(#RGxwS>4OIkCNsGfR7!juvj_XxC~8{iIEIovgL$8tX^bu$miP7)=xq&8-3c@_N)2zwH#T2^ zp^;KRluM6sWza1~c(gaJngM!XQlMg1?k+t9OPI~MWE0DxQHZ#b%OPa*>eLK*Qf2C) zBW@j>G^`M2KtvE(@;wge1ll)4Q@uP9WXG}Gs`f?o2FtOm*_%X4G$TlvZ&0g`@oDnh z>Qw#hepMD;X0Yu&T4w{CO0XA7)*LuL#iRdyEWzJ@BBRRxKt`po?rAlm)p+q7^-Gm( z$ph}ET2CDH?1c4fSrQJtcEQ*%Qs@y-VvNx}gE=bx^E1V*`OC1=%ODHlDM_%O(fnZj z4zuN0Q$*<{(}%c*WOt9u;`9J1A@qQx3G77nqrFL?N)&nuu@}8OC(Olf(i1Yl7u-}h zicBS??g}V}5+B4BMxD&Dh`F*J0FJ!kY7A251led%L_MqmQi{vJ8#rZ-d2D$tXdQP; z3$+-voDxH7snns1pSnG5Xt6;>{}lvDGGH+hLO!U_1S5(x%^T&R{qp~RdmkwfV(tPk z-{1KmAV8KjWjoE@9~uTQ)p*^DDYLHd!9bgNcfr}qAhj*?ds5b7#QhEz8vM?1=~4-y z`AF&>?jPk8r{lymI!evY={MN-0Og$3=|HE8yMJL9L2YYDG9@bYV!I3CvP^) zZV+M-Q6xsY!&?Tn)=N~{wrNZTC8krG1ZOmiCAT$g?@_o6XgoDgy5jNkhkOPpi~#4CSRW>(2k@Ity&JIDtxDt=Y}f4(M103Hah z9Q_}ib__8uvUp(YUtQ%qGeN^?))94k^b>7311Xe`ZSV1B=oh*z#L!a0qQ@4~Q!Ycj zbA9ff!|`IdWvvE_kam<5UVW2H{EFQn;tw;U_W1a82Im&cMfdU=PzY1aUq@w|SoQit zwd~C1jh=zZM3`a8Z;lsv_12&A^E%ZQj63utss6=?eB295HLFqo4{Dt0y2MdFYlciQ zmKepDi;O6k8BAZB+vU3})i|*eR~R+%{vLy*U@-{eaSoLsECwz1wh#U981x$~2F3j+ z2BFIr4M@=WGFO~^0XXwc5^Y#e5b^cQm2;VYB8e1~gbYT>acQ-RDRVe}IaTQxRL&{; z_8T$b1{fSlKl>!6I5kjhe%C@VGuGjJyt0>gnMqWipbce9&Z@Ul#VWmYG%j<@U$x)M zXnl3(QabFJI88e=nB)(UO!)p;q*UkdihwEvn!ioSu&Bo^}ZFWf>2?KRP(Zw;1Ib z5yn&>!z7P!Dz)y_F*i5g^YS!nyTZ74G(tG}jEL}e0J>Xu;)ZNFQ zS=&GimF3am?M@iVlu5Njm(K3D*(=YBl{+~(844tE1jxTWWQjlp#0jFumHKe61zWQJQUQ zv9WCz&S-5>{r33&G4XRLYa?^`CK?&3K^2&T!nQCATM9(xfwWZe(bwLOUx$}s3scr2qvq`m|@AaFGqhX zOS`gbMXAGWBPP&-!Mb<UaOnv7Rk(bB`6r?1by_c&{Jqz!XOaKy+JV3w{OxCl_Qij~ z(}Iei;BSt^&ti#q-&&M9x$*GCJ~yBSG?7GMVc{%DfdP=dW+{_@^Papc)UR->4y4U~ z_awL97wi=xVi6GJL51_Bbj^0dmBPuY=v*2+6`&q_uf|-%LDmA*j#E-IhbT;v|o+`Sz9N7lnbfZ7?})E zE>uY|yOXmoB1X%YPjEtT_VHz(zCjBmuTZFxScoFMnB9#ODdWY~o{_`Nfun&>=NB;e zOh(VHN`^yH1Q2p(=fe|spjPQnz-XOSj7Cxi@Ie^bc^FlYZ9VR#Jd>_AB-#|02_hRq zG=HoHSPPghH+?z;e8t9YokTc;r8upA20YtE6=mI<4I#Xt%!oR$f1a~M3)=uOR|HwJ zZ^s!d>a~@7lQhwV6VUhsc|r9!XCA%g=}gt$tJo5=vb=8MNQBG@_l2B$m^7aP0=7#u zKh7Q9Gy^V+=&z#_gefe#8V94oWxy^B#e3HOjMsSLh!DeBUTEL zB`H4=PE-7E=7Umq_LB;OS$o`l9mF!1_u0V7l_z4nI?*Py82K6*8V*MS=BCWEKe1?5 zvbru>al4#^YPIX6y||fSH4xP=^Mi^Ol~LEB4e}nY_0s6x0RB_9E@0%KYga7(m}(B2 z$?Qv|Y0AO+2oOP^mAo3}{A)A%u`!iAB}6YTFJX?i?};asRq;OI7V2>ekFNdB|v4t#|(N%5b$ARFod4`uhi!^_(ja0CE30I zmSlz%Z_@p-(`*Q}rIF>4-UTJ`BX&!DH1G8#TxOa%kMZ?`m&!Qi(yb83&QU*nARWe? zuC%{XgKBo1LFm8@H;Bbym@60X@=N;B#!v@;tn;=U;KF~t7^b9;FL|*ZkW5 za_t*g@gKDE;MCOLY2|?VprZlC7>n9+!si%9xFf7c_+jj=mj=BSpd2=sIny+>^S{KK zE1-*D)byU9Ak|)Bc684A?DMy~Vg_r0q}+^_@S`DZ8TQ_2;) zu-Bhj0N=o7b9!#aoaJza*Oac8yM%l$C&HC|$aK-lo8re7*4A=(Gee4?drd<^qFCAJ z#sK9OD{F|hYA4J1j5m|Uxz$(q08}o-8@rlsRw&0JDdn;_w}D{#^7Y&c1KJ@%izkK0 zIj7G@qB68a|H@e~p1}&5AFGDu&t<^}czoY~_m`#rg>-@!-vfSfCh`Am&7fJUL{Aj( zOCQ`zR22Lm(b7o`3s!JA*mCd8Q2xY|{#Ggh8!D0p#&g%>-@X^5RsPm(WPd%P;5(A| zCeq*T$iQ4R4bA^Cs^V2C34U60K6uYBY#byfu;Rpu1qbohBcRDIJbd@A72Mt8$Jn(0 zC5OVYSV02V+7<)IKN?*i=7ac2U5@v+PYX`Z_P*dZSTd;j843H>L;wBkNKk&_Z8U$S z{>nA?gCnp*+z;|Z)yAri6@#B4j^wC?NaHZc>h0CSMmrH-hiG8`@+;&{aHNgPd&j}T z!hXE9I~Y8V@`&NVJzP+Mf2a8H*Jt~6Gk=4#i-i@VJ6?YlqrtT1ziA@Zh3gi}3Ul~G zQCENc7v*(7Tu{>Xw|lj2!i|lhujZCaZ{Ab~MxWq<^k)QDB+%>sFbozL@9;P5KMh0B zpIpe;5>vKZYB;P6nrg%M2i^Ry;-EzVuF+FW>-hIvCX;k+8*XYq~2ydT*GjxAG z@)+TN-yr_Gup6ZKR~A;u{UGE+0-8KmCZ*-aG;oOjG%gY}0Yv!Ay3B71M9a7@hjg<@HVB74<}a9RJx= za8S~Z;Xv@TXeeG2nDW?+(Ltf)jwq%!ZDIv_E<*`5r-3XX%w#W2O(oK$aUxxucX`v) z3-~^FEeVr3=7fjyI6HAgyk08^O-j<&iB`+&BmESB3_4moKp8#o>iT-4@46j;og#ps z3<+P0U=~BVDC*(>oPXR2CO4Ee~Y_cDHO@0wSvs~+vp;}wZ` zX3}O@?Yfg8>tr@7`K(t045>;_JMXIJeE;Vtq#dlj@Pz_<`JK1?!W3V)Qx~qYtIca$ zhh=?C+`|QD&LtRHVF}*2O`(6665V@ZtmO}DcbtVRF)^oCtqM=oh^7X zBh2a;M=7WOn2z3lwL|>Fg;S(aPy#2160*D|+VM0~8F>I0dI-Ku<1#o&lZO&#$p_L} zc-Y$dVL3SMvAl=7`+-_!RhIRwB8Mgs7M5(o>0{}eJ2w{YqW`#DB#f{-RfznD!&tzO z+<8KBE7ZlPNm*RXenp{F8K{Wx&s&eShUg9dK=E4*RZ|&K$VU#3v7keIU&OXMa79e< zi%}yF=6{T7it1|5WLj7!+k8+<&n|0u26a`vu;A%#o8+sUB~3n`~5SG=3+QS-vt|M&_=M zU(aM`2#}1F|A<123NbCdBryn~#jAUB9;;R`FAw_3q!JPjMi=ps2|l8%)9!XZbdY|F z+N)nnDN9@wgFbjFY}LoWA4sV0LG;a|Jrm+n^@mIU76rSdUL3#QQo%}iorDh^jf@(V zn=&*yCltsPj7a}-*NdoLFB+V&&N{=c;PN^z!{n}FIA0>c6^WCa_&|VWUFw2SMk|t6 z7j`!!Y<{x?jJM!nt@%K7xmhz8Tam!9PLSErnWjOs5GVG1srX(qCLPZR2HnqKBqaUo z(hq@5QC`Wu@yW>;Ee=bP(^wY3X%@XD;ISUmnvk2GSg7l4ZPkQ=)enC=vuG}tO5UxS z(;KWhY@POm1`KRz!B4h)og2fiK{tp$zo&Vw2>dq)&hfyxl~H0tG>5ACcLGd<57drYX`(<_kv;B>yBRfXyZ zRbqe0<$s;WF9A9rNgr}Nl0Y7Qq>Z`Kkr;9R^io$1Pr!tKw$_oOcGIr-p>S;UBo0ER zq3=^y*uK5rpO6nc2;RgWEk997LA&vGAn&b6`d8Tb{YA?wIM9?`z?wN~-E(i*ZeaB~ zql=eb-IUfWHV*%mf8z|9xolki{#_Dm=S@+`4js-_f3@CM_c5o2f?;xdvRE;coKa=v zvLIWrk+;}vwH7;vrxZ}g!jh5-%0GS9*3=BZ-O;4htyWoHUZ>XKaTy4s>fcDHaE`8) z7WaK<6HaS_^cWJB3!W zIPGXP5SP#W_{8a~G!K<~d+Z5kYa@{zQyPm%^wT*FwDsh~elv;el*rMnD|YDRusU*Zaq2zR&6h>I$QL%(WOW{lMOaf2j1SYBo$9z4 zUZc+>&}31DK2bGP;l!RiL`%3FA;9cgx*TWFePV~oGmNjmWQSV+RlR^gAWYg#eQ<7g zi;vIsidES(c08QLR85*@rl!tgN9vTApYqk-seLt!6n8*Bc<}wC%Kl{-K})Jo<=$J` zeukv;c&_3>l7+-C%@dzqZ}iPxPGyAnXUc_%^O~|K<}BwQ?OsbXc8XLZt*k*QNGbbe zGGuKWz;us`=UPvQD$0C`44sQRePT1XdJ+51dv6qyIx3;iM=RZN+HL2W*JIr>l_#%p zJ_}E;^+bNM7|)~ap0pV41(WhuOYI6x#q%O~Ye6$r_Q4-)=i6~IC(<+YzVFv#I1Z=D zi=8y>tjYv&ydO6z8O!fEMvpO#NuNi~SQ1@{F`1Hhv$4)RP+n0|5;D)@+^QE{@;IaC zDR+O&#F>krTm3}nq&im#WGJog=*J+7i)YMhe1g$ywa33?2ZDe6L^Z$$7#JPd3jvYb z?ja@BN0Q=lXHxB`QuHy2f3+dKD&%kk|o6O6xW1QSjuI8h%L6pEP~eGklMde2z4Mh1mYvuakld2=51B>enABx z$~a`=7UUb7I^S*(qOg#Irz&iyTd}9g!KiFTCku`b-B)P_y{if<<)nkPOX+Eqwu1Re z5kPVau$?n_;{R}`{+MW;aSg;O2u?xgA#}Q*|eLm6-^}f#2qcwk}+D5!y-O~ z2)$Z}v^=eEJ{tG(?2!{$HR%nF;V!e3H6Knxf~*})$}~M0Ndjaf%#6=08r!3#2G$qV zR*2{oY&nOU$M{-x9z0-vZ?`*2$g~{)aR7({+liXyUxaYkl~fp&IFmtu{^mL=IR(6^Z=^K(Wxb>{-$sm zyoIs`r)zcw%*!28Z$?)yJPwThcLyudzE#Ax{Px1TT*xj!)>m$3K5YZJ*9m*n^wSVt zg(=F$?B`p);qLA@jo-Q=ds67)4WCQoW(Wk7Aq-}8*0nBnGVq)XpChY>)A&@iwYY3d zI6hOo+~>*BgobvxtxPcpV;$@0>PLHtwQ)T!08iaTHf*{GbkhU9O;)E~&k>S7BjmCB zqQxs-*wkyU(tNIar&M) zCX2r${3KE*wfWs8YQwe5d7Y8G3Rc3Jtko6ow_T@p*=UhKk8GN;tFkk_XIFDgYbNda zA4-k-g`O^l%)Ux(ll;qzlOw_Wd^z6l)dq<$+|{^7cIf>M*FtV=Qbv_uU^R#eUYv4* zMeMC%Zx83byk}tXM#~)c?)3T=MH>9f8v-QeQ%|5Cm`rETG_mln-$)FwX#+2q53YQ2 zSxpx#_N@>?HM9|g(6k9U!=KmrpQjhHVxc|0*n=-wg)$evXoicsQ^ylp=blwYRgSYi z)eO;)>Y^4!O3r!j+_czZYrxkrauKe^dHj6=^V~4{feEq-!P$yAkjgT+Thm=;ww9~G zcfPzNvqae&;chMl$Kyo!Rk|r9G9ZAZ$gcyUvATeJi-K}e%oyM{^1wlr!M2DEN+rC#`r;MSS~f!OQHvCBkQ3Ac>~pY^T00L= z4`0hASYF1WU0kd+NX5oLwEDYm~$qg|x-0Hh7)x9cs zWYuUolf}<$Ikjy98}`F9U9Ay5)`4bGi&L z>CA1hygpW&y^czfNu&qUkF=D-9%Wo?A#(1Am6oq}&fP+x-82i^Ro%h}b(EhY>eqQq z4VTvL;K=Xas?w^W2}F5!0PWmuawvpWxD_<)2fz$aL^{OyMBCaM;ZGDnFa@^Z)S%{% z3Ywnmt5!m>BiVGC!L3$rioD|Q1@T|F1<4p9e7&boqZfZ6JN6cj7h;P593og?AE`!! zSDxo{52 z-<&|g2*8+d#R@3YAWeAQ&1%IT~ zUAXzF?fpo@@^lz4z}?A<<8tPf3@i5D|xVE@ZbvrNUq)5=wM@naU?&{;g2Tm|-rI zBTGHdADf2cFg&QHsdpJDTr${~v@&hhlYx z4M#83rM>ywe{F9NvU3ozHAMTQhQ_2K6&lM%f=`;GESd|orho8wSKee95G^+{ML0D~w%O+-1YGr`7>-x-5s|THleq}`E(8=k@w_^_JT;Tk zrH(SwuzMtCc`AZW=Oc1C z%*X4Sg=?Ml{oT%DG}DL1k7VjL7tdda;WFu`;t4;@@s552W;*M>8HugMr1!AZS~@(o zZCEHX-PiQ7r^s?G^S4L3^o};GL&G)~k>xe5U+%w4%>OoAK)?z#=`{#>Y3tkHuGrfn zM!5rsA;eJe#$#j54c?vAZpE_G!*Y`3FYq(?%}*+XVGUM$U7W9hwq1wu`S_ z9K&gyJo$WrPyHHBmOocljdGB9MX-ge5-Isp~JB?o$oUR9y*lHD`&aIBSmVd~eP)gwpg5 zm-}eo%?U*l(7*<;M+tnUoS2cNx8=g-7L%+a(!=%Fw+Mq;S*>J5Br$Kk$T|^I`vh&@ zgtiLP9}M5e`BwN%x#(TXF1_Zv^heLn>7P@9RUYBw{rzzR>xyTk`obXx)vpx}VB<^AGvM7DEly9ZhS#;Gjf-+#EG9^e@X^*^Q3K0rorjKQ|V zZ2CrmwujJCZ`*}od{tZpIc*eIEn|`%&jSe>>PJVs)<>?6mUOzNx^+$g83?4o?4n00 z;S8g)Pk02`HA!Z=5_sM9rgZ|fwWkL@OEss-P(tiL_GKd9k>+G5M4p11p|udswWZ#L z=1={Jq03>DzojFzyHe?z+B#O{sK02q31fsZT`JnrnKYhOnL0^xUp2Zb7E}|2*QXB| zbgPsg7JkJfW-Z@$mHnD*N_>N2UQ(BmwC$|P6H_qkT~#?26rwpuTBnyW8_{+t(`|?X zWKpa#oR(sC?LEJ_;@3zR>uUhNU$5qXE0J29n`LWYAzlAWW6vlZXzR%tiwyi zhfWz)R7tH}F00(qW+VR;v&k*eP5|AVN%S-Zdr&8}``hA)a0j=IvHp1vulq@8Ay%bU zf|f%P%gkWZjmwLUj_f0q$|$x+weyEBKjTDVTc=~QdF+~th+aaW0i3@K@n>e!@P{Fi ziIHSO(72`^ghimqO%`?Y?;|7qWwgIDlLQHg``3iGaPy^`B=w^}^&e$I#H z7XWXl&)LcpGVfZ4jEhpU{Dj%wUx{!hUsI`m{EM`T2MXOqiHkx~wx*a$BwD_2S zf|HX|5?Q#fPhL6NL9o(;`|VLF=SMwh?AfifqPsLEUCRzv%UoKeR&_RYZ(ZtB`1z(g z?QvN7F!IT{%yjGRv=n5+1@vvqM+0;-i{gsWjWS<5rhdzxpKy%(@PW^I+VNfa&RKVs z&SWYWf-zi#r607X*(XHt&m6rpw~WOD*rh}>>d2gy81DzH^_G|>la-=0tx(l0AU}MV z_C*-pw9kZ~VxcaU!|1caB#vMqqW>N$GUrdeXE!vjja?#_W`nud$MOd z{%#rred)4hn=Xj-XtSnjqQilVA393a;cK=xzsye12)Ws$9K8r-2OH>^%JQCih6MTwQ0fc6tEr}%e@j1WIZ<7Fe>#Hfq~3QMU*X9!0$DeERU2L z=;j<bz3)0sLwnaB*|F8eW-mls8My3Ns+hb&U| zR8F+!75XRcoVQGQG;Dbc+Lrh9q@Ar|p6bu(s8kBtn8)}-+0rEwWh>EG%lXY{;}=iT zWpE-G5A&cRw{O1a*20v zWHS47g6*K%5<~&IvYYqHHChdcP*(H89JXdc{c-vfnDpv1l0mg;5L9$ox&F(VXli8h z^m$Z%8?Wn^biKX#K*U&b9%9~)lM8&4)=6p^(MW~q1K2P;FR_S(92?J`DmWW{krTHJ@J+DT9HDfGG$rJfz6TbagsMg3<>w^Zi2RGY0I;fr?aL*A3m-! zc*LlU)qiKF@*Nsu(#s1j%ovh=8Tgn-L_^Ot1*aspux}?eSnXRnR{6e$Y`(blo1C@lzYexF&-Ipp!FhsVTZElozE{MGM_J_~r^%idG1>UuMcmNY;Gxe5U;ZaqU zTmpAFq)uMfGSB1UoWtZq;4m-e<(XU{JT+z}KV5fZLGf6OPw4&3_jUrD(>0upG>1G{ zA0ujA25b+9;BM%`&;0#<&jG*w-g)Q~EEBi2!k7|e!e2GultRuIXs%H)0~0fI#$Ha; z8%hpFT_?;c?n!-DJ1W(BExv{`y=r;bg3Nl|fY~ z`w@PHWbEc79n0ZYrB_|v=s?e!eE#}JTp&T*Hjfv#I&F39A z?G8*0H_2BQ)AW{MYZ(Go4?8p5(p*oQnV>5^*hkL--A-2;z6(^Fs#l#~Tp3^xdv0RK zRJ5QB*Tv^dti1b}+@IbMJwv8!i>6vHG_`9SYv)_`O67Zb z?=74OP;%sAPii;WEPRS;YW6CXrpm0%HRd@E(Dy{%^eA{}JX+CsS|u0+p>U7noL)SS zn%hS>a*2NzYlniY5&k_K<;RTYtDDo?N!0Mu)#s*JKJEUR}B0U(#IN+w%$KFS*hck$U7`TsMiFbTj`O$1MBIlPAw% zHq@96Y^coWYr_Ks`SY4BjUZq@FsrDCN06VSh0nA?mPWwR7W|l*L$!R$t4hJFMwUAk zj&R)R{l^=BzI%l(I4+EaYlDYe!F@9^AtcaX3=)ENpDY}Zx32ez2enEHaYce z8xH484|-8qbSoP6tGm-q2WLlE1QcxQjJ%CaR#3&9`!qBCcIBiLP~}{u)$+A0!|QIN z`B#Ln3H(p-Q9IWc6I z?enHqCou=tl3X=%@^ouItk^=sRt6Ep_S~h2P4`1r4%U}gW>7YYcpsmBIDF!d8Ncc7 z;9Rv|EfaWXkCL=zm*UfdWpc@)AfR-l=+CUtNN=8Dwo@(g3qrHk3Zru4sQ8l#OEZ#=?CR}k4n5EY|dIf zme8DIT21ce;dGo;#qBO?KRR-I6}`4~jrZ9!rlA2>`U3a~)8GBXB`Iyzn9#k^lkLe& zUq3?21Zk-x7FUTI-%Yv=XAR<~1M_#`GfQzW|8N0Vs=#?Lh}(9RfAtpp1bHh9de9MWns+_>{0H~O zOc8Xjd1T!V0$u47PW63c+|$w6My4-vyrCE85F}?l?aawh()F^R+_iDX`Tt?>J;R#X zwl+{j5kx5#L_j)76Ht&&07XGiIsxe-z4sQHpn%e)N(bqo_ZlJ~z4zXGC_x~E8t%$g z&vu`Ee%)X9xzG29XN4p)YmPbQXzx4b0JJq?894lSrNv#*M;Y?|5rcoWTWONTR2g`G=SzJ^ z>IRk9WGEvt z^l9z{UvLgaP2snccFoRSYj81g)edR3kS4^L7OjRjlAnLmzDYyF%;S~J!38@=a-?SR z=vkvWx+aIaC~Q&D>@>`?0AslSdKIZnE3nMb_`kdnEep=@f;}VW{qys>fR6z&9}f*a zeGItPQ9pk5508X@2q>5;*D~>D^wj1h$>sO{^aiuuFU~2IE?vBgb31#gfps-Z6)m=t zZ809F@og*Q;;783+?EO#k2<&O#P#9Y{x}>*#7Is)4xV!d=4^epAxpQD#tWlAe0BeM zPWcl$U&;b7Xm%gL>2|Pv3uF=NJa_+YL(ZU_pd4rIn#JOS8P9Y3qk|LOYYB=fD(NBu z3v$v0NsRDZbC130Q@uF?mHpyxM?RW`p3A|+DzqzwIJ_si9-jyZQ4gbq{c{Gdan|g- z65K1hWiVUsK< zF3_H!T;*NWFmQC-Qo|b>jo#Ky&~$IB9+O-Fy|La#LL-k zZv%pZAJs21w7vq*Hozpc_lT(yhI-$nn|*4bN6tm+ydE%a$2A-4vme??g&c3@(k!RR z*YkDsi1nzUE9&9hIoS<4%5n&HH7&b>;%d!8Y6Y;(3H1%X(j^Qm7I+GQSuYfE2g$iJl{8ncLg>U$;47a1EB#uDt zYpF*EG(q8A4^UTPuU@?D!=iv`$ay@x*BFmlaufy-yc>Ct1{XzyyD4UprgV9+ zw?5`Q+nk*O>b}82db-4|CS*OB;&FB^q*#yz8|2sxucmZLGNze(LvVkiSBu}{YMqOkh2zUFS?7jddGQsiWDOl z9W~>sWpEwogHrn;RKvq->uZBt@EEUE4n^3M1s~$Eg@QaTd(UUS{CLVedC<9E*O763 zISlSoI;~V`8AH)Nygb+okMLUI`fO=dfAm>4U9GprZs4j%khbMS9hmwqa(pkC$0c3D z%o<8}?m#S2Q<=y>F&tBRoJc!e>!>ujN)Gb{qUN~AR*Y%>vsRnB#CvFrlx`=Yd_x5( zaT|iWJ-%_PS=^OR&72lr7|F!H|_SHo9ggjaTXL;!W1^1H|p1 zITh)@LOOndMi(@XC^^D2cO?SU~q86;_<;_zp=oO5N=2yMSP?*LMP-n1MhHHOudhC&mK!pDWfRo%$hB;prEw2Zj$@iRxh_^A`kwX)OWE$@ou9oH;F~93IIznaE`Ss^`OR?nE6fnW(IVa!77smhz2P1fNJk>B? zcw4824jnL53JG(OGt!F;6zxT|){lG~H~1~0X*@B?)0BbWB6~RE5tn)oXpeDNLXB(D zW#&m~xM=6T+}Rl{W#jT+o5;-ySjtrS78b>a6AIQu?);mleJv`wU<;VF8U}tDc?#c;b5U7 zQMq_lcmi!%k#WzPKC9byWuw3U1o|xZoc`;3h=`Nle>UJdal_kfo!#MedDls!mCkdL zGP~BrjndPNCGT?WbQ=O&TfFFUhnN8AT9ga%=$#<43?W9F-Z{#Ak}y}!TVSN_d&!>p1{Zk8i(jMn#!(C*C>as znBHxra#=NLS4z!)b2)22#F5rw=bL%3!WY++OJ=V{sRjZ*6`zZR>3W@L>Uf@cl*))F z_c#s0N~fGhyE`5=jPdd8h20Q+=v?N8)``vYyEc{AsN5r+Jo}FFycgm7g4nRq9Bq{Y zeQH&sMho&d+u~v)8Em*a(WQ2{j$r3hna@0Z_`bkvO(D9ZOOcMUHtWZEbbff2ah={} zS_i6ChZ*&(HrLezBaVSdM4$G;ym>$SzJhzGD8`3$p@=Qlr#{Q78be?@{x!$GSjsh~ z26Htc*@;T7FRdgr7CRrSmv-0;xvGKHgNT*atnCbHoI=6&*g)sddQ`x&f@#SQ%+(T8hqIFjAxVivtJfr+Di6?d6Ev0Bq zC1q>N^84?2NW~qi#*{zRSb+`a`23ozf&{v`dCBFJc$e*Su@V!TDn0%1xgZ&NzK_ah zGuW}1?9lne&VOWFIykP?Y?q@LT&oc@_m>Q$t$OI%J9{GQ5?WzwAa_^b*pwndLV5ur_0FRgbY79vr+eay}ZKOMME|4qhQW zPRq^ZUmi1|pRXo!GT)okT%ejKqpCj^C)7J=lJ{zB(%-fvlv}?wLKLIz9^Eeywue)Z ztJ8-`0LhCUZ3>4b1tvH;sG|cMC)!&nT5SpjC0ISrrn#V%Nwe3cb#$9@n9*PuDb?x? z`~fAn6|#NkSqPR|*h29c_b3{7Db`h6+v<;{eFRRy??_R*~CS^CS>14J!EfFmP% zgKw0so(q0d{fz_y5w>TuaGSo>2N#o8Cz18m-O|MUnf~ZO3Ave} z^WCTKl_Bv9qA$A^*EgyT8+p?0Tb)LnCGi{`s`kgpW|S;R*>APX=&SH26RuQvdm?7? zIR(&<^{ZYPSH1E&KL<;wcpfhkmv}baADxamI9m`Y!cgl%`qh23^)O)~^f_HmM8ZkOH)J5sN4_52l zqXNy}YyhNGzREtOW_cixk&k0(Js2f_nFKi4@G?pE zJ)Fx~n@;zC7LtZ=_eng9`>Su>iZVY-zMbKzK=gN*^G+Q1P9N7Ik)c%f{NcXW!L@z@ z_jWA`96DMB&xRb5Dwj~@K9JWM8B(aM)T5O%nadb;<;Ra7k_wp+CEoD@NY!`^FMyf^ zwLb9?NNc)LgO_)GIJcdeSLC&+tfFGSQkIheF;y-Zk%aC6>X(j>lA?a^q`}PA2rL|lgcpdL>*+TPV2>Wc_Vh&i3;LsOW5TR zHz~y~aYi+Y)~5AaIbb@w9y5E%yUbadV~iSDN9 z$MWZCgCE(B^w>&scep|1M1OkDkJr}TMPj)4xt`h$iBa@+E>$phr zmy`cPzkd7kLsB5QjQL3Lv(oujx%1Z_ar|q*wbF|;e`fP_%oG1 zZ}iv4p1#HkN8qP${v7kyuX^GF#WAL@IKPAXzwe8r5xYfN=>FOw|1Ab!^c{9U5L`D% z|8+MPyh-i=xdcDa&s^f)(|M;02*UGI_TRttpQ{f=z)Wgwfdbu~$bS!f@$xAFNy6^S zXSn~C0qqB1CgZ^pe^q;ai}a5P`|(4}Yl<6pmjmF@v{N$>sNlK$V4{@*VBk0JQ~ z^e*jCp%r42|4P5dsBuO{PIpt0Ne!xUV-d~`y73P)e;0pg`P16D-5xz7vl=6NR)9h7 zc}dA|sLB1yBrN)X88W=yGyiWxMAk`$8zer5+p;{h+iG4h6TKl$YXaDCAIbKR|1m{7prUP<_IJwpHp|SS z;Cn07U}#?2KZBbX{Q13^z-KMe=(m45LGSRZ&$3Ct2^3d^`6P*bEweI+3?yYBMwibY zrW({)1iuRe3^>`{JHT1E|IwshS9$>I)?s!3r$U%T-#IOjKR+X zZ)nUD$Sz12fsvm081Vnwhly9?0~#{^OG7@3r&G`~veJ!GP%~=7zO*ZFnSA+U9^xtP zmv0ViFvA53Z{P-s$j&GK?PR@W?qelp^HX9093-T% z%)_^K{Z|IL3=XJ1DsUOyjV>HZvH1A6sKvuB?^~nHejJ^DdqO;2$;=Qii+ECE$RDK+ zXr!T)M*-Ep!iPp(Y=q$V3lDoK4*w`!5XS*lkwb)0#93QQrY|j%nyXp-BSp)gP4UWQ zW?Fg?L{TGut~#42=K{U`W!y01CNP(rxcI>5|C$Q{Gj=XYzvl7)0A>eMbMSfTb&XU# zO`vVU&vcmyf1d@Qcj?bXWw~{cDk&hihje-`l1ToMg>Gz-eHZ~N0O3C6V<+?dd-A`o zqJZ5OIX(1iLh31!Z$p51kOCyTB^~-;_rT&GnrX&X@1X8?$s_6^qb zNXxYGA&?U^8I8(_t!uy0t-o&WgRi)XLDJBRu5%EapFxJOwi|g=WFjEP{Q&?ueHHX= z*#3v=e#S;h20!IA7BsNvW79p@05FjJOX1Q~^ai=i^s1SjCt? z!Uzjj7A-op0hOxezsUR#dH(#lNO7!@llqCgiQ{~-t-eVU&!9ejB(aZMb|dP~twFnV zc|XjfU$NSbmPhG;W{Kw62dzuCPq8r>+XqU2Wr_bt^FJcO27x7?e#VG9>?v-sP<4*Z z>`GyfjBnQ6fhI1BKVwpSMent25kRHI3}!<-`X!OdbP#C59UMAfZ?S!+_%QgdZFli< z03)EQD?eEV-wNN2PpTNHe%S2(xUZS~kLa9a0!R9);7v-|LgmzHnFQ^%t0V~oFuwVq zKPUL}{=L6ph5jo^IV8=Fiev(-Fe~r#Eyo1>Ipzo7cjFr>I!SE@uzo$;o&-jmWiCL| zPbc5;{Ad1uj~)QL*HOXnGmBODr7O{!3HamX$upS*&2O3hFv5C_3Ji~=zS7%+3riHk zW1DTJ^oAS~%&LK#X5)tt{R0R6xr~>eV3+a5&t*)-Ss|fb$D?OjRCt!^sQ&5CeK*e# zMiRjeUbI&+381TP${H3J+y$~3a6RVUzYPuvODv$`lc9fQGu}^eyY!BoHUq<_v@oQQ z)Yo}H>)r3&PaAZZo_;sosN6!Ys{WzbW4ZDKK#EcJmu&uR^~F=30lv;c^rwN*!u^`I z%UI3Uv|o)=jPsuXFL*yBd+5J0nMlo~K7K0k>7lh8@Q6|Dm_m>6{)c>J6tMz$`Ii9R z<%+vo6fm|dw*gMJ1bG1%`sqK1CI!p^yX_;vlJhs|_u7{Dy}s}L}fJhouXgTkzL7P`!{M5zk zgSm5MH(1!QF$`0;?cX}_`^tL&>)>;Ly1_ZNZ$(rEkMsVRW6D$fha%3tCX$?Ay8vSm zMb3-&r=$NRGnv0sK<2juy3N#YrV7PP6m!Yg+2>Y`=;QvOVE7-h#GgG&8p?|Xj(gge zJ?o!$y^+9+V+iatXjir%$)6wnX<8KsuxqCBd(F(445dk3YHW^5R39ydy3(>~!3IDOm3S?L_P4d%B7vPlV8k27f9PHpUlYS!o!ytaB zz5qXIs=Fw7#*Iz*{l@MQ{5{C;EBqQjTjK$$3O4svyQq^=HBj`YMiDT4Lt+R9Beh=S z09iVlq`5gMHsoA)T`wnInmr2C3bomMB-P18mP7~6z3(yKo3xCnCYzlVAgb$j7Er3UM^!uLpRz4Thnp@EVT%q0rf~_8S9FL2xKoKJswv z?sz4ab_H^ZBXHQ>207!A;dvThcj4@n*<@x$I-{C$Z$C8LbMD>dc&S=6hc1I=MW$+G z=aLXf_CuW0?ZSt$?Y@zKn&v1muUz;aVq?WROxvHastxHJZUDQiukNF{%-nRT%8(Jq zkh?t3`K)Kmx=+}@KWlB%kC2kiBcNJib)0k}=QH~X@0M|d5lIA5@U;oG923Sbd;b{B zb2Xm#*^`U0?-6LPZe!UX8Q*da{d}Etfd!c(X;+e9_m)Pm0;w3jM!L=2%K@N0?k8u3 zyC`R#sl3_wh8f7wr`*^~b~zq?+T^9${E%HG=O$f@u6xKmfa9V1Zm{kH z?Bswlwe{%Qo%leXgYjx*w`#=h9%F`NNw3FBj>3Y^^Yiw=!uqU6l@pH^g z5(ct~0*wSK>6Wvs5>l`54{PwITr&38X4;`*7DMUW{pg8yC4%g}hWEiJTHp~)bBcPu1!m#pd+)h_6kdZlNC$YGzpsJx8-agWH>Wz&iD z&t0ebA*YYbLwX_ebvxe|-Ewu3Ig2>;0D=~`_54lVDb135!=sV`{kUD4y9c@TY zf98m!rVFMR;UPA4_uL(?+-)bDb$g(H<>KWIop-ZmhSohdngG(9N>#E-4~k3E;ZV^ zLM}Xib@Vv3Vxgn6CniE}O+R78`+clk>ooC4A}ZfT6#!v!ox=!=_;JMxTYq*xZvG48 z2~{3yGF;bj2yxnI4vT*~*StO+kt1f>$d+*${pBr_+wY4cf4Y}!@}y@G@jXzMuj3#vXeF*l0~ za*Qsx5ytk zq&Vi!o*%~79c^cg)M+-SG!`2jq3Eem07&JZLUs4Rzz-}t^a6GOE(+7yr?jk zVh{oFiJ~fJ&!?(u+Lr0w7sL>4X;MzE>*Q_U6OudFnnc#noM{itvf4vT z29!(ehBfR*4jq#8^*|q8EmVkj8m`MF_9LHFANug*<$n3)^2}#y`zjn?RE~3-4wK|!e%#>cNX3M$Z&thlUYD`qxt?HRx^(DRtpiYpT{6s z9l!`B=XR(YJ_)33{K?MR^@nwIcPmc@MF1B5D33ZHl7W~hPkU^ppr)i0Zg`HlK3-;+ zivXDh0))kxRlb!*E3;Yv0V6Ms=ga7DCoSrm{|fl>&m$c1(kR5RL304_?sBDAe0*CD zGmSZFr0ngokb1FQd2vtm>DRrjm~+VT35}PbWpZ@IOQUlh`EV#WqP}&>KX<<)DtDxF zm(-Woo9q@9Lso)&PD<8;E z7EY5fTA{06OnPLu)<-ug`}HgRaGxR7@<%_%45My}@Q4ToVW-V-usL7bLI(Tk%q6*A zfO7PiLsL0ezuatGOQ_6JZ__@W0%P0UQC?z(5^C@2asZJT3l}OI1rient7bWI2+9!R zsb|S?=(`jfvX4N|@@-+G?o=s^=wvkJX0B?~_Oy^ROo&w}O|sE9viF;t_d~spZe4_@T5+3wBS| zuO}dkySALgSk%n~ni@0poQcEChM^STm2O>nr03ytz2V`#`VfchAxX1bz8A0Z%*HGx zE0nbdmBwMl@~M+TYF9d|(|h?=1r;S}Z(lx~rXk4xCiuZ0h?#awn0s#i4)%ZRGXQUK z0Cv*pC;Igb{Em#J;Bk~ubG30Y+iLE4m+3&+D!A&6ub}1}k0_@O;UND2MS*Gmf+07+ zIV2bwymp)jsY@6}US7nlDkRt&?%d&@!91%#j;))QU#|hbhwQ5lzD_Q#qg*?*r3@r zFq&cm#j9)EUadNfshl#8f?Z1;37J`4M6Bu>x93`Wnt&}wE23&b3)QxCnqRNv zRx;<>z4HZvF8-(SHOHCVnuY-F^rF?edcMGx>2_)X!DVgthV=niUj`3ZS688-@}~eJ z>C)(G>Ygxqz?>%g-Mk$+=+ue*OFH_b2QN7{WxVia+nyYr+^pK{o_H2z0Ziq1A7Q03 z3UVf_wyq-}J|^nAQX^lgldr+M9LLV7&>N~c^6=A&&{}T0MI9_Jro^qQsZS z7WO)q3@<-@0{EBIK7s@Z7a)Bh2z6R`@INf*EH)D+`<)59crVB(o2mj&me#x<1Z%CC ziG}w@$LP5A#-Ta)MQduOT&6Qd7FKlu^84uqjkyk)!`j-hIcM~Yl=I%}ZF5Py51F~| z`oA{8t_yAmT;dfH$j}|wco<~QmJhIA{I;3ax(4MR6J@<81R^jSLf$nQ=WLy1A)0NJ zX(0u{X2A)y5=hXHa`NPafsap>I{vZD+qYkXq4wZ8ncB-j>q+htWoB03*oo`c_5>Vr~9QnFzNEPdt zc}!`d7eMSvQ{x_-w)7I(ncp<+Qs;V&PM$GFr8APdPdW|&9Eepq_h@43u1F~wuP3OY z@{!lN2`RF)4LJ&yQpqf$cal6Oe%Q798k3tlTrl4Ju#OG&*ZX0mT3)E;o0OFLc_+>@ zZ(V_{;l7z_5|}Xi61JS4!*;Kx3v>@rfnF%fno-+O65km9Q|BmD9E;ZWr zqr^eIQhk+=IRM#H#7QykF9vlFK1m$j>1H4jV1sMgSBxcd#PLT>f?uejA1+%@)Cq#` z%X#z|7usjcYYiC3!#DImIEEZRZJ^}Ns>?h8mb1A}hVV%JUETV9^$aKsI$!)*Bx1JBX2^n!KCqu(lrJ)&kWVBZ8=aka@~Wb@pmJR|8#5kIT%YcH`ny?1sprJ4fxqS|9FvJ=`+da(wz*Ho$buj`nYQQbdK_h^^hbx zKY?gYO;$_I%CI5JNpP?9d3&5=e2~+&hl-l%P*IZO3LeqD$R|MmLv^*_2)DLP#a_vB zZtw-w8na2t0OUSQ?>o5l{yRpBamB0S7cVo%T9CN$`oOD z3wS6JKWTe%AlQL_J#GWY6P5vs7G4NXW+f*lUwI2pZZ_>58xtTGwkzUQN%8btUmd1L zJs6se0oG8)nRjy?Xpboifxnse;@)Z}%XclEJMGz0>TRNc@{62Z*C{tjoX^_eP;Qb$ zkU)>>gyps>_5DXyID>Rf_Ce6B{2Ws#UIE95keBZ<3IU}a+Ae}n4x!E!VFR$ehM1DG z9fb(Y!8}a#(Idr{VCseblSHE6o}}6rKNSZo99#)>(Cb-8d4Yl$Estj3kqU+(n>X>pMQ^<2gw||M-kIkitWs%n zpWe78w4XBBeBEhlx>i>*BvI+jSgs@?Wm~r1`@DlekTD^pV2*fpH~B(eR*CyEMVeGj zSJ$b6pH3#y+nZn85Y?Cw1A2&g6gt1T&y92%@h*^}_E5zYu2>%)aroJ2mu%GyM*&S? z)5JXauG;u3XRB0I{d58CgGSjqIt8tdYMr)eFIm@jQ0VlGD){j0IL8iOnyf*^FE#j6 z?~VBjE^-Nwc5IpGNYEzdYusa%K-M&`7Fpzu=BYKL#(;bEPcDsv#T~crx+D@*m!q?4NwJA6=h^Hy?#SB1 z!KtG_LMaa~H-Cm}7B(*4gNj^se%gtXTF3BG5kL{WvK}Z}8p~y@YlR!fyL!6Qldu8B zE#j!?IgzITMyaw^dBgY*sPlJvTZQFDS^h+WeigSeG(KlTUdAETI7|=3t}U&vZs~k^ zbsZVAGnffae~S0Ad$?uwIav~`agvjd;!Lr93;`2pWg@rpS9G_ZE`eJ)^XO^GP?*)q z5>>YAD__B#w(xvxX>@CH5+^ zt`qAekN4^7gA76TL!jdIzNsGVT9KaVg!MwdFT$D5@@aT9pzjH25~H4XFQk2xQ>Q{Q zpQj}Eiiplo#0^ZX0C`b_v)E~4d8=@#j-nV!;Y&=mn}+|h>HE`{hVG#@PZkTk3_C|u zD$QDU#V3{3LV|+0x6{5uuR>Qrv$KLAt?3=NE3aFJKs&Bt#tF04d3hGqZif=MKA|H- zL}Ycz<4ge4b=pjVDv8^l6eYR>bS-hKdEgm!Co!Mr6s1lsQGFlo?f!8qd6&Fq00sw% ztWZnmC~B+bLc}m4FEL4bg{=iIo3O!q=q-aQ-XtVIPc@FxHZ+Hh=hGKSNch)Qja3H|*?)6k9#Z;%j>$JYXR zJaa*`~bjce9?yq>Cu5ZRkth8yM@ zEDRZIJEuz!M9@%MUB}I2=g=y9l_}@dkXFz0y;zHom~Xy63p65ZoY0CfT+!X_prjE{ zb1N&|#xr^n88f=_3*3wFtvgu|T$T#=L0qeS>JdUC&_(c#6oLe{>6{qp=mLn%IUdImqDGF1@IqAcn4IOFaO zpFQD9gLQ(mO4d*UQ6m`!fed2hEGJ9BttI`XwIuCyD8Ywn*<%q@SVm2yC{h}auu!cj zGj5Q<%%sJzQ!Ta>SJZsyh3b=H2bA+Wssav$_FkW_LNWW}E2|E#gT-U70p*CUcELAe zKvA1uGIR0!|8O--K<-vg_)nXl*$*>k=A9ag8gUduFHCZ8Agf2}i2Po~KUN}?x}*2t z&%58!(h8ySGZmUIP7hux^hq6k7*;vO~=uiI-N zquxm5>6xio>Ct2yOm-cvo?s9tcbb+Y-`iQlzD1TaDc>WyXP~br*>aIIlhQJuXyq)+ zf0oi=V|b)~+OM#T?T}RfnYwTwK#JH}^Z)ibzff;%x>6?159O{p+ECpNZ7p7ASJT|6 zbzIN(8#mYQ;!^H}xrvps-APpdgL-S6tO|WnI5M>3?}UGOUH)xk(s?u7HuO~t%xvui z5~~k0a=jZ#gJz>LpEVF-R7Mtd&pZ~jfHFuPq8Gj&a(Wcw#$}!Cfl71VZy#0lsPsYG zGu;Kr35I6)FK+e~1Up#F%1q=cSON!$CFTZ~^kAs zH&^C4df&lzU;s8~28!8PGB)sK5P6%=BAMz5tKr6Y*XD6}5XKnii)i&p*rJ3{0{Wc% z{AyPB1hjT{?efif<|5%UU$$#ER85CVVvFAj^46G*if=OOE9>{%) zAykyzzl2D=(}Aw<;4wcG%=0{CeNdvt!Gaw)P9h4xyY5x90B0qII0mR2^ zHYVN@bk8CYXb02m88}ycz2|+0f;X@5z&;DCO>B+YLc=xom{?sZJAwMmgFP}%R)-iM z^w9xAA9dA4P2^ps*jT*$BZcd7uDV%{B^U1`pl5#`-qD79{>pmWuB))#powF*$EQh}OxrdlrhCyU?!PbxT9S8I==;+@c3uZ*z6M{+$#%_n z%|UB2pY0mr|4GZ(CD$TyZY12y6#-qvl76)4~7cBK(9IfP(su!<_pJpk1iIc zw3sbc1|oS2@UMgfh}ltV zALf-nIdO_^K)H2y$KYJzR$Ofe(E11^q|#4!pUrL&nJ>BP=`#RQq$@%y`XsN8q)HaRNPr`wv+< zUZp&VZv-N(nGlS~l_`g){k4()>`XX2dgi>S^*)twS(NYQIKr%?mKnaMv#D^PTPlKiTwE!)k1aa>mDWrwNG$|jfNhRYTcQfike?Q}+M14JlIz<9LH6XFcf(SmII60+V!mH5n{{bN zwJ3N%W`!ctBrbq=d8l^hBtkoZ9#F8gfOGDQ4dSjpn%b;SBuiZE967MJv~(-<5p;EV zIVY?kf?5LSPDS$KF*Vbuj_YUe@@LlG_L61xU{}!fMf<^4%?%@rsHP5N3uHc{2Dr9M z>4t%sEo{1=Am6t(Y+um2ZoCc)+99d8JaEy{vpcC~Rqo zQ6JF9^RUrb%$Sy;N0QxB-RgxB`+QepI-n9Wm4Or3lhm8A;Sn`iVanPa$5*1dKC@7w z;?h35w7G0nOke{#2HM$Wf%Iv0PY>w6;XaF1Gt``L3iViz{SE~$|B#Z>?DUwnety&w zE4)i|f#@&9ugookXXEH3Pcy5+0U15Jm{M@k3lH?bP|d+;KvNmprmy73$H(mkEt~S$ zdirv7vL~HJ3#LbozuxZXjGa?a)c91BN((Haf(dR9od}lrCnanf{Wo~^`|6!FuuChh z{iD)U#?C2#bP!w(iU``6Ej3QQ4%9fcT*qDQ7Jk5j4b4X_BOeiqZ6kQh#2&0EkCZ#H8BEK`V2vLPv8FF`S)_9ze&x z;gUzCI3l(9jn5 z+pgQ~?d=sK!q+~1`XosVUMVuI_fdU*)%Nfj+3k$PYa6yQx)Md(@WUz>SBu>jp8^87 zO*~KZLkMovZy4;dRGLmd1uz!YPO!XQ4L==_1KAV&wd68LuE@!LS6F?=;QiomY=b|v zQKYbHNknit{q#bry;$?r`w+ZhciixV*1lzgv^4kf=M(S?ZZRgo4W!p{HQDTL_MI1Q z2xR|agp~Ru3@G96o+Ot7TJi*4;kar!svhW`_?KY~@gYqx8%wDVqB10ALTtpop&mS1 zzy5eV>3F-QmuSREVg5T^h|NM!%xsjHQMawtw&I8vUUO?}`!1=u!B!37h<&k0;u&aC zg!A41yDAIY#I;DX;hT(ttcp;zTqtDcBaco=OUtv|)D`++1^F9(5>{3P2wO%eZeqh6 zo2Dn@$({-an=fOg-O!!&9^?h8QHIaYAdX-z8E5sFlW&KuOBL~j;Tq*iL59}#Cpqrt zi@;<3kd3BU_n2d}9V&_ifR^kemt#`(B9k0vUj2I02!^GV4K9Aa8I5|oL`1Ves)50n zr9z~5uTB>y87-7gRCAof9(5OzHrziAO$dEV*YscV7;p zSs{}tb`s|wXunz$lQD^>#jvGf(wb0zykhK%OylZSUmO$;+iM2s8Fb>X+N(+k&qW)yHcSk7vVay6iP+WVTF_(k8R^ zMEmICQW>Z#8o7Eb_lu$;s44FA5~1Y`3u-OfE)~pPDh)wrdA4AQG#YgPupTRb5*#)M zXO?%V_gT8OKR1>JXe8x#;6SjC^}et7YDiwV-OthE&yp?kPj=gEk1@nFGEtvAGEO)d z>;zN`F_`N=WwXV!qBpejvH6-ag0ElV{(Z*pb{aGDvk zUj^Xn+dRFo@|T|~rhGlyCVsBplk*izlV;&)cl^c9`dyI09wzY@OsI;=yri;9o_a@K14FR{UqqAN? zyI085Z&FrPNr*=Vb(`b0qo@)nlrF0!n1UW)knteyDP1fFu%i;cU>^RVi}~LCx~znV zRdjfI6C?JV2qHr&?%WrOIJ_&88RQ8AAmMe|XBHMgHT=55imqd~ z0#}M=Oi*Yj|Ebwfw$+w||Ev1*b>}1F?Wd$FV}=h3wQDo62}%#b`Kw{RTAm7wN;cux z!dpB5?TI?t&;yS5dTl`^e zPv8O^I8~-u{+?*sLU!6HcHM1ZH?TJv@NAYGZa;?60FsTVSaF$)pHuDrf<3{eVHE}akg0Bp zPp6L?vQB-X%*rZXcCua>8{INQnusojtL3uFWDE8-ev0>?@V?LZy=CcUmXN0Jzr|UdTG5&wKPT=d(T-DoJUx{z7GV* zJTpgg_D6e#ULYS6;B2XkWJpK?WY>12FB1|kMZ@T&% z>)mxtlq>C=Iwe}L(}Pp(G6kKYd4E6A^!Qh;KnU6BHzeKAtL^gZ|bv%RMt&1pzWOLG=w z5Q%(RI;ulDW}9t(U{>ihJ-KF)MOHQ{p-)&d!_bxDHaA{mTl(y|q%;q2r0yA{Jm1ps z;ZrZc~D)2nu+SeKrM8nE*U;8^F1y2(S-~esF)u6+k zttvXhI6ud7BigPvkzZp^=(N}j1>sH3YGl@h?rP{KqMdtdTUoVPRVMqZ-aW;9R6&mc ze8`BTX<_wp;2Fcg7sf}19-EG(B(1s49%&V^K^yrDMV%TQq~+zEIS(|gQVxVS3pBAz zl+c&ECU+n}gi1tr0A#G@mGPD79d$=94OX{71FyBsJMVM{ zA+a?{Cg7wc_H?qmxkzd|C1T<1rlgfJ!wtTEx^LBcWSzupVdQJKDJhxJ(~hM8W3o^G z)-+{2QT;YRc|;87YkyN$8@V_|rsF!QFAUP(AIUrzml6D;EZOozy~e`#hqLeYdJ#~X z7zvB5=>e!QYybvTtv-GDfXmoNnY#PitGoCgHjtQyDa*In699!G7){suHz8St4XaT0 z|4}F_E(OF;0UbviU)V%B!4(-TE^d7GfeK`FlsSJc8Ttrzc3Cfm4i*T=B`9WVWyf!& zy$67~j`T8&K(gQKqs9SmtZrbx;s#Up=R+OU|6JodM7z4S?>OMT$Krl_& z7gsJcrEEj641{5mZ>GE;VnSoKnCh%{L1-(BR|IwakN#GjuuFKvhj~JCE$kWo437;| zA?Hn8%bnIvmi^=n7#SjI7l8SA`e!XGS;G!qy?7Dkji|B&AGTU{s5ayS zC0$jJ#5B||DwrFJ%Px@N>;wdr%g|0P7Mnu=?9Z9GpvzKJSA_csD%Id^wl*@7AMzkC zu3Szm-Ux$8d5O+6dtbC4Kdwv3R2Q9w16spAUCH^tIHyqGGICOe!{ek@xtVQYeCAPF^7i&<|lBY-Uwq|m%%T@ zj|DQ={jo)e+v2I#$!S0q1|eI6z3~57VIdn~?Rf~sB%lf(JxeG0v|bdlj@hvG>g+6p z(|l7D$u&7wmfvQFL7a;HdiV0^B)Oy@haJ7}V|CQmvO%}ELtpoPk^o8ZPV~!fPD3pM!R8 zES91K6}aMAph6t19c%m>fm@PQ&C}X)o}E}5(}%RN0yy%%$O_Ec342i2u!3hNOig`n zdH$76#{w2OnBT1xdnF*)O#qO5pilX^$o&5fUVdGX0%X9*>F>W8e;=BzMLOljz35yC z)+(f16wkDnsL>{@E>m`jw0QxGGsFK@>JsgfDMsjg~el)84<| zf=`i=o;{NEkvzUa0d*cOulM@w|Qr>zib(PmDkAMP14f8`PQR$q| z58Q^sK0?~+r-2jO>*`M57^Uz*URMB#ZF`#lEpTk$9apzi+s10D^7qu?+=_&0SyaFg zSI%3pZLZx+9HTGfq9@o+v;EbF^d>w`27~7}_R-L6%bZ@OzW;}^w~neZ@7{+Mi;xzO z4(XKc?mBdrbazRM#GyMS4t;2(8>G9tr5lu#{@u(o&dl?D=KbSc%Qfq`#qH0Y0jqSs3Vo^3E77QPz6k&}A^NsMUAg0v1+^NnL zy8BXx7o_hk6`HTRE2<$P?^upEgMh_}g^Fi3Au9lF14*0f3 zQkxzP&8wN;)LDHJlzu=U;vY3p%mM(5)UryBnZFOyf8Cip@uRY9=r4&(ev1HSmOlRO zbV2UO>9lVFMiF~qI_1T+HsL`4t5QtPW^}mrGi67t;wZ$Fnin@;4;VyVls4SLaQ^(Y z+&#Cuntz1Pm^tVX4yS+vLijU~38@n)`&@$GV?$NVfvc&^=WVzDPul|!^gLqR(SIUs zzVe>FZ4!1>C9&u3Py-;6H3(?2GndQLnpq! zHKvV9N}}X#bX8u>f4w`~5|2002%cJ)?1A`DKnfrJ(zN=@W5IR-q$Zp0D-~5BWyV); z*4z9~8!rD*$#LL9`di;Lshibd)mf-hcDDwoSYvzZihQ=I|$1;=TLd(MW?SP}`p=RHBrL<*mM8pXP zV&LKFci5X`AYH^_inApoCB2<%VpW;8oh5mo(;@@dF8V&!+P@!BnZQ^6YDv$nvwr*s zAiBe7vF=I!7hMAh@GTmD3mbgpKl(a|ba0KAC`m^;@+2jb`P@~C0Q1_8%XubEVu~pG z$Cef(wXLdp3WjvP3b9$uZ>Z6RSDJL8!F zESy=TJq1igkXNsm|Bg+YMDn^ow`5h3lN5&RQQIaY!1gcHv;M~kxkGZl)qQTw0=rYq zC6gtw25D;DzA{(#TiO5uSDg@GNDTNVh^r=rw6jMO3Td#04qlrUu*&^o%#CjaCS@5w zr1wh0L-vh%l)}{$d;I}YIwJ#}kPi#(;q>QWuElo@e< zCWb&q(8;Nkn|6&ZbtF*{&iO$4ISJbL83a~O?}?)TmHvO{_}c(<_GXT zkKYx&JOP9J^_dkzwI!L>c3dHhXr=Pkd=lAa<@d#<@IlE+^A}4eJqKM|w2;5wB`h#g z$PGwL69^k0V4uqZ7C1^d_s=oofBsW>q{r{&^w;;gejz0lPRd!MCLVcubyyZqVYsau zY220?92puLhZBrOi2F`~aV#c2p6nGeK@70?y6-8g$Om(o$0Tt)mOjJjI4v|+UvySl z$x`a)_2-;U!0DA=`&Qg9=`l{$h4eqF;2mnX)qZEJzwa7D^wINT{_?zYgt$_CEnxJV zbIB>y&~hHmWDyzN7uG0Xx@GaY8Ou;#>(&kD4ji1-si+|Kc zpY$FteSGPk|4E?tvHw1ox7ztHFZ~GsLeEM3b?r@rxS${j&i%=3QWBDOE=cRW2DMi0 zVbzwOLQq6R0`Qe3@q6q(-Cs1?2p10OHMLpLX8oQ804PBn?o<#zdI29!;_TGC&l{0l)z5dGVh4jz5Y+-D?i8|JRKvy#P0^Vf-DBjjv#G_3t#P zM1GHCTOQoY9&04}X{+JJ2(W`Bf^N6A$TCNO(&V{+XiKl4eEdLx$;rPjU1 zSM8eI!-|CDfIrLRL5+1KaGujgYV}wo)APfrv3VU6lLXJZ^_6L!f6i~}7s{aZxlHUj zw|o6u3WyELyK@z$|Hw3dnpeizW0}?cmrYIm0c>yNwCI$a_V)KDFtk1ld_fiyyfn%6 zsCEFk@9^?Ihb%a?GwS5HE2>yJgFvO8_lJ;a&-d`A=?+yQ;^G>O*FS50j8|G-Tc$tG zze1shQ7IGwL`XJusr1`_=-a=eP!_G+MF=a35Xk_S4>0erpa1;@f2UufPoEwKo3x<* zt}G~vuqee@ir+19RaXtaQ#pUaYB`Md_${|&aU>?l&v{%}9j8e+opz#nn6_oWY(qJ2 zl`n@y3&ry}W0I41%Zo{wo87Bo8>%hU8O(*8 zDdqSnVf((vFazlDz;K(`C5zrsGU*Q6y5YTtk!81UDjLo9x00d)(PTkcYs(&yR%Qtr znseh@`~wPGY~n}p?=Gju^X4iB6TalC<|TMA;MF}9eIxoN{^1mlhv7JF5utZ<<;rs{ z{j$l_d&S;1O|P|QB(kCT3jW3OUlZ*NJ{ctouEw+RlsbjaG3fC`;NWJ=2By=nry00l zo}$==3;4qDDVJEhP;r|nedfeSKU^J{AHNQ3G<|%J_v$73%cr=&8jv|6!T+=K{yz(f zp1^Jp0slK!n-&(Ts7~|>1_{n+`ejJ9z^=eA`+1KaNgZy+)4_Z3(nWVVW`T?LuSdUG&Rg$ z`ELLBGbWFdnaj7*7oH@+Ia1!rU;GMRn?Fz(z=ugbZ(K>^%%q1kk^`Q@cx3;xhVSo3 z`>)L=pDdpfrAn^nFwV6F~D16EJ4Y?YfYcyqa37u%<*B!da_g|ANB zuseEmLZB)>wyY7%B*5}Ql&O;BgAJlPQL(Pkz#$LjtbM0TeM}G;sdnx@yG_pg_K&d% z%B|Zg<^4=x36uc5?*V=4-jCKAE*$&Mm)h8Gcd|I+mDU6*!e>Vh1UjwhAGyH;<1Lj^9PvwUO>3_fBT`5z zy)RcH>a*ogdbJ;)ncg<+B@EG%fBAzg{iMC%O*-&>zX~8d$3yEaMvTjowTQO$_3Y-k z*RD-7V9;iYVCIh9Pxr`ok7rWO`WVzTn!*{j9Q+z7d;54>7@4J1Kg^MlK3%nJNZmeR ztUd+1($3=gK+b$2V#(&>(;jf$Qk34K_JYzhz1;cg!tvF2J-~MQWS7J^ipW=P9v+I? z+TAU{GSG@p+yD)k@0od*g;<;tB&XB2;>+EsTW_U<`rzS^gp#(>2xFuI<>!6ZN4 z_uyvun806aLjU=L$Pm~&9p8=)JR4@O?+yXxG1|Jj+QiR zWMpI)$-bh%A18y-Pic+NE@@C%k|zq7x6Rh4y|%Jm_j$N&=3>h3V|(c|1Bip#ohhEF z@yqst;+)9xV{#bR&vtt*It;K-8!U~p6apAmzaX*GKI)!=gKmz-A#puO*zI8P${jYR z>Ddby4PoG(i&rBQ1pj%ffBhyh1Xkz7x1;{^p2r(R-}T4SBr--qe6-})Z5CM~Y)!kl zl+(}EgM-;AZ+9fQP`nqfl>_m%htonedB=Nq_P2&o&L%?6yX{p?n1nbma5R(PU%njO z9Qb*lik_K(j`Ce@vp+5i=70&){vZziVbdZh``f1{)TpX)FwgFGWHvNho^T6e(QAJV z?Hid%Y_BWL@w(}tVjT0P%pE1+>G}M0uW6DQdSWac3r(aD6^E?<%$lUqP>Pq@RQBE^ z^hqfyy5+sx$QsXWrzFiGa`SK_8#(PXZ?OBBC99(bPpw8RSMRu;C!I(?5uM$W%0~4D zuXFun5!dRf_^wntM#?qc)u~c46B$*027hCYHguE}C+Sl_Zxl`Ij221@lQ) z3MER8q%ucJjI+yJ1p7qJ#Z3)oIWn?2AMj3fXOCW{FgUUhpo-q+YsSOBLHRBX5VfJw zi3|%eg6(4g7|3M&tuRhU+O%mr&VfB4H3i%G0!KYe#!~VhJ`t|SF*^BkYB#-w7B*6+ zqGg(1Ipki=OPWoj@PH}3UxF$}S=z?F=FFrdn0~d`gm*%2O<6g*W!l^F+|$>u;0 zy|1WSbHL8+Rp{Ol(H&OP z#Ti*H5Bk8?S|rYkQ|pzn35p6d=b~g-rhd0`D&0k7rDqBAz7*(g3_$~G*62h`oNh%1jNhjCvTESU~_~3A60&`%JO@o-+yNcBg;QhA;CXW zVSg;$adlG1xbcHJ-pcSZ)Aj<#rmGh7{ijlO?&mHb-wKDjVax>7b}BmeC=|WpX8si2 zkEIx_UKxxCz8_65RMDPx15mmkKi!Qm)|_VoEDgxk*;4_IzF6+Q)|}hxy6e(uGbC3c zUpMVbVfdtv>38*zU{2d5W&GB~BPxsTl<(!`SOu4##xR|iaqp3Ag|M>pe6)m%RLh{= z&D_|Ib-l!W?icvx3VI?S2ELUqLZRw`ZZc9*S*$+R8OI5w-w~~QQtB|%mbiAN&Jn8x z8E{v5Z^9j@?e>-q5exfaGNfsh_m}kc$ZfZz)d0^`aI_q+BXIk;QT~=X;sq@~gP!Px z{|7|{y`Dc)GN$#=tL(swt)YkZvtu7E^JQ^89H_|bd*^l(=i&hNG(8{Rlg(2_V_f#* z3{FCNmc7Op)Tg>PiI7ZclB4>bih!Um3Aa@-HCELahP%~4vmXZiIVTTG)C%0ELd0JA z1^W3itHZ&8cguY!-VRU_F9+w{G&Wth~^O2=ae)kdi?J(rr2 z9h#lVnwP71{0Il}ESXK0$`%cmZeFW_xAwUoVr|Wq0%aS%T)&C#@w%K=J4ODmn#B1t zTkrMYr?@z-MeaOf;K}>UG*O2nOA5==M0jQhag- z2XH$Z>teT+2whd+IUK`bp1qltX5|w-yEu$7#w3xab6}y?Fb)h$Z8xZ$P|b}%7OlHc zF4|Itx;Up?CVBSUHW(n@0ozL0@RK0*eG^#ncnF7Ftz#fK$YA^?UmAm?UoIl%K!9fOty-!eqv4 z`^56dNMzpY-n%oDhngQo+wzW5GEJZdn?)%I-F4fM>!Y(C4fpVPg9c22A|SQ<#p#Qu zT7WaoF$u!`yRUBG{o$)L|MXQURd{eJ9Q64hP@o=g*{36krmo6*-L&I^pXl&J$M3tc zb%kMqg6*>1D#=3%IAk{lwo4ZCt9Bl{bQLs<(L-kuhYLDnw1ec>{67qc=|3s*T@?BBe3seNk~v#n{S3w4xFM72Hzp^ zkI}NAxrS@k$Henu&FY)|+A+7Omzye4@@sJNl+-CqKIN;Wc^MPi((=OWq^`}Pctx$m zSfMAfF#LX^+>6^{GNXnsN5bXc#AG$b*7pr@S5}#K&dN+N>)wux#Ttgr2*zrh_BK?L zx41NoM{iBF5}X#wcl(QGNwyHOyu8fv^;krb|)yc_Oj?lqt(#tqlhnCxf znGV`P$ud!YB_O(fjbfPlDT|j+8vIR8Tb?vHbS%T$mgW@}CuVeT*t@m&&4LVTRRxuN zCSwy%3k;%p-1xjaT$JgBu0>uq&Mj{Ay(k|@&Z*vKQ~cBlQCgk!yJXK%)AQ7H6(k{% z+OY4^5Q8=!B(T}w+DXmuc6&Qlv7i zcsP_~Wet}XjkrZ%#tije4B8xKvoze4}&UZ)`B{luk{O&HKGG)X)i{njC-3zag#d|WPxwg7-tzSFv>Ju; z?#HRrb@YRzbi?uT%gOtA)37{}Uexrpth}yJ=ks8YiY*9(BmPa& z=w%dz*Ua1WRr|`hAk8~e_PWet7v{x95j>C2qH5`Gr-iOznDbuMzf5s|#$6?A{hC6i z{o&z%vOOa1V7-a|j)d|qBIYVQjiqO6E@pSKOiKw4xq2U3=he?lf6Rfcccg&|-t(B% zS&eIYCOWDWk@kWN&d`1{<{01p$HZ=IK7;jEp%SBwcOSKj+%SeLA+^R{#MHV*JpPwkAQMfG7S+RJ2jg{#Zl$zDW9YZ_ChW`^^V8v#Z~sx20D74Qa|G$=jX#VOXSnQ*3fsTdR;! z(M3Zibq0HRv~t0o3u_Ex`hw1)i_ws&Y|tlGM_B#1=j^w2&w<~sHZwA zZ)<}LDW=3K2i4|#8*rz~`XUJAsLH!cTm-*#!BDEjTItGR^NUymyTocjO)8!RHIqh& zF!0Q1rUCof3In92QYZ*>fq@wXjx-!i-vMPMv{R6T4+T{~+GRgqB^zGPj}*;*T9u$D z^~|}7LYHyIQIvy>){wJl4XQ`0fV*=PSdB)|+~}MUg3)jHv1?xnuR4Ie%XzjjD^oPT z!*T?dq!!gM&rzZmy_@A+(};k?3|)VU(8uNjv4#Vsn2y3x+47PeR0^ZNLV6ZsGo&J= zO99P5D%FPCL82H$`!U)zDCpD6$Rq>wtRgzSNK8*1(%`Qf+nbhQDAr6Ps&pxee(tRC z9D>hXFYutG+PZQsIcWNR9MA5Wtg2Dj6ooFT*Y@3gVy`X27+Q3te2QzF4kq?xN0?`; zPm7crRaMBEaCp7*a0YIB0I$Xbv0BLYd=zs%<`f|GYDp5_&216~UTH~bVzJ!OP??6h zDG2cEBVI4iVwuVCU1}Gd`0G_rA6nPdb#rEEB*!b+^zv_uB@<1sA%TborT!EoJ6|69 zW)3PGC(Yhv-YoBq3Bsj_NU+~F7j$#>C9Uu}Ko0poN^o-ZHMY%T9PCnP$`|zzC?2=k zwmc*Qjh+6rL=JDY4aN3IHQkR=aL1%!`@QAN;Ws@70u--EJ{^v2`t02UxtIU0!XUg& z@@Rc)2xr34nqd9sQKmKb#pu}_MEz{5xY^EPJRgbo!c1wZd->e)G5<~s$aX3SUsJSJ zihiV7A#{NBp3)VFRrl|;ykU%i)xAN+u_Ye#%HM6i1^FmW9Zot&>9D_Z9&aOr)1t_t zLZu8&0xb?qmr7$INupmZqfl_U@ejBf*79}GEy53tq+*^7C!Hn}qr8DOx*YtV%#xOD zExQKys284G%YAC)Z)R_bM2qOdf|&OjMil^ADqubfqx@gL z|04|aDm-E^_1gahNQkFQZ6od(B@!eLhD_nQozl9Uj<0UmoCcp5hbnbd=u7Qnfg8Nn zUzRL=jb&G$3*f!>)Z}Y!I#CSG>r3$-zu%h@(QKhMK4qcvGEv5@T#w@rO#S}Dba)(q zl`vs23W@>{WAw^T4cuw?QH#RA0uhsjzs@p!D6aBN5Z zW@qwEVQp@)*L#x0#De{(ISgR7Hx!gPw8t=&Bqo>ITrJ6-8GZ>$)I5cO9OZn`ES$L4v%!Jrw-GZyhJ=u-TmPFg|*? zO>F%1i+du|=_M=_V{;mV?paoFG7_wJ+aTNg7Zy|K8w>l6q!Yxbkc5=N$R%-F4r8xQlHS9p5 zEIwE}*8wWmYxCriuL3jG_bj&U3k%BA=Xy4mZ&*FPGIlvomM%XN+c!8bU{@&-#odxx z(`K?CY%JXIi)_h4e6VJOu$_?DCG*)Q!G~{3?C2}k^!sBlh;_VjR?CHW|HKDhUC(3% z`qSd_7S~e8ew9UYk$5Z|)4#Bk772mQ33<}#bt)dLg1;GmSfMXW=I|@Nvh+J@NBPFN zP86?$3IZ0g1oK@V;AY#pGOvpxXME2PAbb9HAaku)*l+i~KaRejbhkd~txgW#&}B~L zjlp96z0|?GEJ3(;o0R7tR+Ymo;;j~wNS|kfkx8~c;IE-LT*O=v)MA2?m%d!Tek;6f zsFeC;&`UC@SN~aK0m=T5&#C*w$!jU-E-EN_dzbQI_8szgC2e}t*AF`GNYyCd-7BpX zkCWvjX6rbI_VVq?O|#-KY6cZ1nc(Fd{{4MR?@JD}90~5jgmu9BMoVLJvRnw}|iu z>-pi~PkKA_w4L(9>Jol~YpU2DK26cNz`aEDwsGM-1HFCcxS8pw49w?)l=oC#!KD zP4c+U{}n;5zN_i+R(xm9m6+HVwi{dnAFWR3et9&0-REmA%}}+$L$c!#yS>8i*ce7n@Sr6g23l*T7@{*w&cpw%}L403Na z_j;KNSs+V|u`?o*eq*RGy1uupYIt7rS~73j_IP=}BP)!Wwm*vWbJL1inIm^xn#Dmk zp}lGB4-2|4D^TrQ;|s*ICn@ zl&7+^SNPWoC+inJxX3ly*z;9CbDg(*n(6N!7gfXs1pmE8P4?!+!Q+EVsON_nSc{@QD#X>RbbgRrfqG9*7uh3Wx4l|?NU1_gxVYidp)h;)5UU2NXH6{yUdjgvf&jU zaNtzYf3kTq7u&%=$Z!7zkh2>Qe8z8H&Ljyyh^{>TxJ1NzI_70`IWl;?1X<$z;7t_8 z^=Q$i{y6{I2m1F&u0oTL#gr=)?uG};5Ar}JV2+qbL0yV9)s+jivH2lZ;XF$l3SW!# zg{`2^M*S*W$?BIN|3gh%CTx5DjzOMc{d*)V<;8rjonET>>f<2$)cAk4hU9vZciI+{ zF&AgiQGg?8ELZa~C|D>b++UW<{-g>G=5V$dB7B2i<-t4n2dcv9C~2{DLTu=Gb9;rb zWjwd>4CYo@!)Qap-F5bcZ95Hv#dg|X*Yh53*^YdVoznQ3Hd_!y6T3igcJY$##^NjH z9{Hoidb4xL{_ZJ5A_wm0rRE03T}Aza2qSFOzNLM$W;pBR4Y?ub?z!g!@pQ7sEvI>f zOg9}2v_htwmZp5${hIv=R9#0}w%9|dI%;0BxejIb$JqKfH|KjY7&LJTmwTfM2`T4d zJ+(~7h{ppdmu*ILsqRp5@j~r_)WS_GBt^jy;lb+`8zhf9>l=c`%w4CYNj}j! zjYbm{Z}G(MoLKt4wozT`4b9A}aoMhsS95XRsXBW04*jtdkvKNXpBZ*n!nMvvLHsut z2O{y*31n~>oJqnz>QffYAGA|?@{L1ZO`)VUATbG{H(V1W1eQ(?!P=lLpYKw{ypZI* zj;MmQF2MZW4hcgW>viA2Q7Kc@4LG13JobI!!;kckOfs9W%YhE!^ngwmsH80)-BxHu zYs_l3Xk&Z3a21^-JMu&67F-IOn)y%gZ%x2hQ&g@2N+f%6t}X>JRIQF8+7y{yWIQWd zbd7j{*3Mx2@~lkdT2&0p4}pqi(J0jFqbnax&b|P!U8+~l{>9Otcx|8gf4~I$Hc!EC z2>_T-BO?#KcylQ`{kYL7py`X6V};(3kEnmI!lkRI zzy$5`rLxs<)f;Z}ec{@FmpvJOF0}~+U9`(oH9f12Rg(SkUt@}|9q1UNBMA9L6X|=3 z?7&ROuKr4jD>Jp9(Ap+TbaVpR;+WFV(90(Jr!05Dglb=-D9E0t@f{zWw%y;$ipN}yyP9LUglI17N+^q4pWUtZ^O}_*yUwQwD_tD0htTVW zDd{wj1dbavDguS~u4~OAVOk^NWJlw9GAaoU9L0R@x^&>q7|&DtTmtbw!WFFQTeP@K7V!{^m+irH06rr-Cn(ypMZ1QDH)X#qA6&>_^yCi5u{SZug?I z8Z|P(8)A8_iC4`IJH3o2>s%-Ffp-1Rdj#2lb`kemf-a&czsHuXA3bIs{J&ct+v90g z5aypq7L_~^A*-e0KoZ!>ifuYIUvl7$YD>U0a55*!kxMLLRu)402JKang$hj}3ioxY zGc1Q1+iI@0dw^18t(Dja&sF}HZU983THRCOB?CkHN;RN(>8?&|hr?y^X_`~Cct-QB zCwNP)w!FY3ZU$}zGP0#AGx`XnHPzFZc^?4qqXLOw#@pbVW%}3ohNW>VL{sHYbh(AapJr;YMR(W?{VPhdg23)P(rhi711aqg~NX9b&;wt^{3VC&9>kJ zAd%LP_;a^?PFfv~P;CFuI@-QF0Avb0n;(!X;<9y=7@<)s&2PH3cfKbXrc}$Ta%WdA zmQSFLW3WEcr`uo#ar7n$*LMMpUe?HCG}jjuL8PaWT@H>Imb+E400# z&t_L!;RV`0MIvNtUoM9(TX7E4j7d8;y6TJ+Vk+GoKwze@FavO7<`L&&{X)wjb4h+z z$0ZH147BuF_}5RR6;VI6OHy=gwtm<*&ymqzd|qF9%EaK&hoKPJ?9x#D*rlZR&X@H5;>)jqV{4{maNn_*bnI5@15w@wd7Qkg~ z`;2jbY{lAFCDnNx(Ju3sJR(P<*G&hz`Z019>(P{JoD@PGIA_bZ-{S{!SSzI`eE$5j zWim&fHG6leD7eaV$_J>iO)G%$sDmOxc)=eFPjMNd*ul3Ay(u}ooCh%=*15F&8#>pl zws+)}v%M!=`|iz+t{*6hPDnrd|L4fJ+n;B#9_U;EprS?B>*Pc0D4)T!*S)=ddsEa( zBei-;I!HKt!6GP(Kz|bR#a1@N`zkHr_AF({Ze8qLO@FMo<5)xOO}b-VWLDEb)x~kQ zHH&Q8nCe{|$~VpY7=`wNCzx2W2GP+ue_}4o za&5UyG(xH0zCHQEv86M$qG-|(H`kEDbz4^AKq_S*9bemkT#oWQ>4u4WN1Yqge7|=} ztC>F_l(v5$&o+sYowNW=a5Zio?>R&P1t*zr#YOL>&$l=-41Kvw*4d`O4^-TWR$Yy& zVx@pvA{#MQ|0%CRJ2J4#9KA)2bn4|{AHP^A*yUkN8Dc4Y8&>DUN|;{^=oCm^wNekain`S*hMd(>m%H~SlzNXDgfwuplo zWVUr{BN1>)dR*YM0`+WPEH^T%Pex4uhxBD~(nmjf-G)JW3blBecw66FHsahI8&6M| zyPubA4Q><7QF88h`y>4r(u(zpRBOfP&@A)Qi)YhQ6$c!Ru+S6tjYpCQCOwx4>O(Zq{q&&=DCHo{l`tcAX$6f^||n1BdC%gegvn4qhx>u~^kQkD=$J?SN{ z$|l9_x)6CAv8R<7$|315+%DV=&+ioW@@%(QTlc!MXiOt^BFg4Qae*S<5$rFD{xrFn zOL2y_zruwmI?9OTdCX)uwq8q@I>zEP5Z&G);?C;eFVU~YIq^A2*ix%jy-(wC3j`p? zBYBy#Z(fMMIEO{KT@Ou7>yxx9otE0n&fdF>P@@SoUf^Z)O>E;HmM}kZa(EhfG+MC+ z*TR6NeO7qNA@|hOpXJ`(v?hm2EeH{#UmBE$2^tt{j*o9IK-2HKO5e$wHo+eTVJu%y zoDZoLxC|BU^`^;to!nv~!MRWd6>6niIlD`qb6YM8%{MuN-PFNXoD*U+Pcxi9vN%4g zoE>ntE`|SeR$U)s7w_K093yHRyhR+=1cg#;A2VtPg`pQ1SyXcCsJRrxvbf$So~`7S zxM&4^{aKEQJQ}g|O8u8xXA-#R@b2z*r+L+D(gR>5u#E!}8q2VCYJ~q-bL=^YfM4Bf z^pjE8# z?0bxc{IGJL(SKcQ;#CYHqmjPU_pT`41+ph|yk8)oSUwE!Js_DfGUEMvh<*Y)i}J~i`UTwIIELf1 z+&|F?eu19KAoI;HvPjzr3Fs{^Jo6>}sKw0Qx?W4vxrcYY*k=lbGEwv1e$eUPro&z9 zGoPrrb2>TG@JiZ*%n5Rwixhz++o_l8?EM16jK(4*pJYEZS4($f+%7o&R^FiQ1%hyd zd3v7Eup`@_e+89j@rgX1heIazILN|1oZP}Kl*9N-YdE_d;nBs(X8S zdc*^GP7L)E2j$pi(a{u-&PPYvS=`NRS)26|O0td<=xdV-IS`zU)gt!}o{;alz(WT= zV~B&ODiru*mOeGj1|LAqQ_R|+D{oJyvO+8Ikct3ysaU)e2f5xx$(U+8YTM-| z(j%^x7C!2#rKC&K1;mLwQB4X^dfpYpb6}X7BFwF>LDw`w318RnvN+KBtzjyMh_-IOr$eyFDojJf&)6gS5r!FD|fi!_@7sPfekCaX)g za;-HVO;b8%>eo&3UVXgS?b#_8Cs3yM1_(zLZh8Ke(3u5n8<|>Rj;TQ5!I~}k${6pp z%WJfv%Z51qu9dbE-r`v9b}Bc9*T$0UoqPzElFxvo&WZ-x#{5noFNWdd(s8t~_ z;!C^1{DhcRn)$WBJB%ZxhzXihsbkg|$NphR@>L^0x8t`(0V3x|e#kH^>%Rhk0z@}x zfah$^KXNi*I8p2*h?v>sJEy+?1n(Ciy6|TDupj3#D^P(L8+dpjFW@(_HB|-_KlwaO zYzz*G$USUCt}GhFNL%aAGY9$~4s5>Q#xrpmF2~(o&G7F41yR8; z(XE|-0Iq_&D6R?CJJdT`-HKEs!tf8oDroVv3SofoU5bv-J8CJtmpm?7rbZg72F%6S zunSt_Atj#4;%L%QaG@>KDk^j^0QjK({fTt(COuTx;v!7XR@_2`goWdn=Vn&!ggh?6 zG_KIa0~gf*;ioN&7jPH}(w!=PfwEbSjOLBCp>L2Bj_64gqek6G3d>!BVmOm0?qv5S zEE)KVLTYRlWPQpOL_97Ca++vW3lvJwSUoZ_(L%Hv6C{r1u0Dxspl)s5YH#KB9l(`GrHc&gM=P%GcmjII?Ea+(-ex8S<7c$1+9upIWH5Vnzy|t(Ub4< zm%wB7^Ov$}J1Lz0tNUhd8q_4KTZX$USC)B!8srq}YEeFq@ zpzeb#^*4iZfOR=m#K0??PK$#6Gm2C#Kxs{cBC~rVJ#PE^`Uuv9_m)Gz$^?mGv0{Dd z8i|cVPXxq(UPMDXyc1t!$(EEs14hx=J=Yay<7aydQnuokzsJH$fX7Onal+^tO3mb#UVz z1<9vaW90I?jfqPz>DeZFBy0AoBg_1TDokvt%>n#m;yUWfyW6Od`*aad(_Ija2Gm#IDis6XS# z{C6NYS~GO|>2_t6zCF23smYaIuuMwchJFQ*M8~6rZ(a~9)gAB2uGQW;I zG-MP1dUJ-qqG*erYlQgy=2e+;CLXZhLzxq{{}-Bt8n>9#!sOe5Q_9TOqIDh{JND^iPZK$x`*T_dOwx{lE^sRatAgWzGaAe`;BrV%qk)@B|OOOfNbtZgu!+u6k-+) z6o6-RB>sYT4V12kya}*eC)<~7&wXdg^h{Sf6t8c9#Xs>ZULn%qunDA+NoBfT;#8Rd zmBaH@-u{~W%ZvFWF!XTx1utkS?0FrJOr@0dm6?n~un>%&j-+5No}16T(fctsGg;?t zy~*Rq4cKK~(3rccM;f&XZ4or%xuvcy$u~*w^ni02q>&m!=q7jPl!s#E#OdKo+z(k~ zr+|)=U^7ZvU1S{*pOU*sK8?Crj%jRE!5~YSy9QRL=mytK0k#`5ua5TA_a-zjbf}fG zsTY~l><_5XqGhFJxX0A1bnRHEgh6KV|&ZMR=mwC_xw8e=bpeAuzR z$$pL5Vta`r7VJsLi4IaRu$N^=>Y_N(iz%(}fCRl0nI+#v<0ihVIbm?WE)bt8KciZs zNJ|~k(@p{)lHxYSqg4(}$6&`VpfwS8$0H$3za0A8NoyE48?Oe}43dD@j-HUOtmNbg z0GEF1j_j6StIGfWQW1B`s5^DUj0Qo-$wB-Hsdf6s_KBo7pL5J*N|2rB$@YY()LSO= zYnaB>-Y_X7CKVdBjC7p^+>yW_!cD&6#E}KF@)Tj9M=$c#IPIWXPNvD`=WE)BLpf&# zt=j)EJ5$db5MIM(2-14Pe^`wIIq+|7A0ysr_uVn;_k#g)Tq#9+e+w@+PP?QIefxwL z?H5_PKJH@ap2U3BA?m`yMWt;+!>QSMFWIDN6JOiR@1NP)KbVfBx>=cKUgzk}G3Dk{ zfDu`IAR)h``O6$uMv_}4ylN%y7}2+8ot5JpFJfT7iV%^*h^KAnklA>ADfh(ky68gF ztksV#&EnJ1r_BPP*6p@*GAb-Tb8q?~j4DM-w`y_-N4W$ywcYPYiy8F{nF-MSB8m7@ z`F*1UGV`kHzY;{A_@>G{(!+{g{SB%u3&6epk$Zzf-uP@+vgFQ{g8NiGuy%Z#X-!E) zsX~7zc_ucE%t2wTdfzGjAv(&lv`#F?80p0`FHWP3O|FNn_n#QIZCAJ^m_`mrQe)}| zwrm@>3D`R^-FeY$tna$5tInYDNM(sZe54zi0I@&pV$HiU245_p{6qFswya8B%bEY) z#3?E?1**wENUt`97#*65PTVQzQlt56N|DQpTv1K#6gp?UInw%&E7$sKIJbqsdxy`W z?IGw%XKE*+`7VRcsEbRMeL|;wHY@jJobj0Iy;c^IJ*Vc$x)d3n8&teU1{KtY*F~2* zgdSJ!7RD8P*Yz2BlQ{Kp$<2nv@7-VN=1;Fql#LPL_CtsPH_(<_^Q7N&L8vJCA~0R zp{iv@O_Fo~Lh`w25^;nqn&vTq(l$xPY$Zb~ruUU!KxlZCJz4Nh#XIg475%Yj%z8;~ zw{6ebph5c^SE;q%rCq+C zoMNljz79q%yw@=)Nu!ltk_*4C%J46l2T zl#oi>xVzn6!QELP8<=E8o9TLd4Yq1q+1pX*{LFxBfQ_DRy}x&gbya4t zGpkxiZfb6xGO6WQNj2_?VK&X0AannkZV6~!^Y>HA^4G@JP3QclD@$$pFtJEcO`l`D z&Je+L5{2;g*l=wRsVEK&1p3&?XE-1@T;k-+*Qa2RYRCFV!~eK6tj!#t*8YB&q?FD> zpqwu+xJgN#1yzE+)A(*TwK*5kA+s9wflMmj!_kRw92WPr_PJQ=p{{ zuclGAhswF`g#?jAElsV>mG>@fOQa*fmG_PH>}(V;KMEbwGSjBdfBg9HRD#&0^&~Ua z)0z}dU5vE;2#rhjIXd*?aBXa@cWKqvpPhZoVjP`e>Y-=E^wNMBMd}+GzE{L>I`{E( zqh#&nho@m*gEl;_T!O=NbR5O((Cy`jy(5DL5{sp0XB}56*eTp|)_)3ArLni((!EjM&J-t0?^bZzZtol; zIQzJCj5py8ue`BbC9eoL`r5n0(pz=Go()Wdj(4dsBOw5VE&y>04kqD|Lbu`j?*D^A zr|}1cPWNvLT?k^F5ME0uD!7~(D!XuTD)vjmQ6XYTI_O&iD~ciZQ2fba%>=18$mK~Fko_8^g8*qvV~!_JEnzY<0}6CY3Q1RczwnI)0Uou( z0)D_{a&Slh_f@3RMEs=)bRh6820Heq68halQ6f{+fzgn%T3>^aHl;U?1P>t7CxS2k zrTPR)(h4fyZ%+sdwdp65mI6Z6%(hz$JzuZJJ$-=oH-}%NEuSzA^mM;et+!_B%a0EFR^iBF)y}kbs32KeODr0aoNFZnlm86 zvVIX#v`F^d4zH4m8L9bNOM7XWUrjT;{^`?oj#WJ!?h$qrtySDB2#gW}-`LiNT-6Ln zo0fR8P6u?@B5!=SEx@XfU4-bjkwyq9-t9@*d1Zq+{vw2lgLIg2m=W-Vt>%e?xBU*k zIU)J~7$!+DKcak*e7p;CI+q_YmrHDFzvfEUE3OFsq9^)rkSUM*fleY%MMZy15ax@J zYo6{R5N2Op4Cz?%ClgMBL;XvNPM6d(|8_RXTe4%2WPt-lYJg9^=#5c;{hb-_TTmXW zq-psdWNZ(iQ_^5gi)Xo7BZrNfrZaBQg_`xwcX9pv)%B88dP$O>W59o*2qxl?_Qzus z{+<_A_8{EyMab=J9rbiTmN5HRGE@)nJ&L36B8lKP&%0;>SpPCI@EjPOsEt=;#=B4O zU}va-;9uV3=DUTu>8}u2@o10^OVp17|fDWC5c$>Dg{eCpI|Nrfi%-D z+Y3E{CB*7vdP=j}gPSF>+RQ*j*Dv?9a(AHO!xaUeR(&jvX?*7CGrl+Q%^2-Jmb90C zkF-c65p|>HZ&Z+AK7I1&feoc8w0Cty5j_d-pXfguuF#42?b|_*C}fE*IGF?r z-IH$@2Sen_O^9!GYEYhF|DI~qrS;r7t`(Gg`Q8%U(oYD}ai!}WigDEUY$7*y|9;^% z)$qmNAL93Gl5Iq6rWGkYN!-%t&PKIn+FzF_OrI>fA&*hPm>CdtD^ajTmvVfUL&|OT zKh#()XRbn6FK8V0)a46gjp$_fTJ?2(Gfjh&|8mnf^iFdyd5E}gAHv3?i>0zszRDK! zZIoG8T|)WUTPUY;v!BuaIT+?|%`^-;$y7^Gi@BU5zx7jgsA&6eim#YJgR_VnYrw5J%I z-@wo?p1p;G-9Z;4lafNqRZ1&wtM1%DwmtB@7UlfSr2NJC_uLUmR?k64LOd;t?rz`p z*#wAg@T%y#y#9DsrU<$dDB(*Y$TGWItEOx8a3MqMn+uCrwoGfOvq$(E%FhR*x+9p{ znWrDrYFPXWvQg@c>ixSFA_*n}cbUJL@g5{VvHR|bysiotT>_m}+J zb!nhSJ-TZLG(JNotN&O0p7~I?IyM(nZ>qr0(+1OXr_OT;aO7X)hLc{DM+pnr9Z%x( zVBzTO+t&0T;UT4me()wj$^;zyRFE(XaUuJQFoBRgRd9m)nSp_M^z4;}iFOV7#~ zJk1VPC*o6@h@PlqYd!xdtp`yO2#sL|{j$2cj}v&41n_b}Ar>1GQ%nRIehQ^4i`wds z<3Xs=(vDW(&(jL5Qy~&(%uYlwt<>DTNU))u)Qxy!`LfNA;9BK4#H9ZRZaC&uQ=*~7 zqqz!RFD4D>0->;6^TTG^6c1QP4tgi;Q<^0ng%VRMqz`yB2DB`{c-mUqhv4izYd+E@ zGIdZ(H;=Wp)S0q;@@k$mwi(UWskSilR>JMC47d%_CXt(k? zR6Q*vqJ_99v4e$3a3I`f4>P1KA4D+XOZ<3wo0HF=BPE*jHRB*Hi^jj@;eX94lstF@ z(2dy}qW2S^Br1G3s$9VmK5)|v9ITfRA^}{p>j54Pod5OeNP^8F+QDRZW>gfD1DVVh z7HnDltuu5!h7u3GEG5F4s1dkGgXK7-A>8Sje%jvI%7)KZyCZtKkVf3LK>pcwNuFIK z-ZwSc_RE8}R}6!gfAJ=UrAV@F_oVK+haMv6V#BO2<(T+!ZcKLB*izAPq&*TA{L7B{ zZE`CSe0$`ThrGfk$SYB>?yb@jICLmJ4X`sEu-2E4B<>0MVqy<)*3jL4z}<9>jI{jH z*5I-^F(wm93Ru0HBzm1iJ}}Z3$Ej}UP3OSo>Acq^A}-$bfn;AFRhbuIh8FhV?L4g# z<~CU3#ZNcfVDkWm)HZ3MWGA^uGX6`r!mLIQ_pC!~##i!QL9fkZ`!fhOAF z`_&x#Oibz=;oC;fB2CG3dL_(_;Z@igbcYyiX3f;9s*f}HBnzybxM;JY1;iFm_?Do- z!0M#ehU=`=yhYm5KZNMC8p2g40nlw@Yb=l3J_Hn*son1&Kx$VMq}4l1wk;i&+}zwo zcl(rRNScyBy!_B?wxMU1Nt*b6o=RVM@&bL;iZ5TgW*1>Y+vE=SuLt8oe?P4Lyq@WUrFg8R8N^rDn549ObcJ_7rFGjO<6j#t zX_=I$U~`02pqR2dkHAf#@gCjSQ<()k_>=&ayl+E6&+7ZKcMT4X}ta$ms~duk^kOT28tfiPD(`^WN6MyI9@3>6Q_*_8JL z({GCqb$~F*x|b>|o|aJ7_tEM0tBc7T%OOs|;oKM7*4?A~KOMh49ZPDUZ~C&PFD!cLDCo_C7_pJr6yPu|VI6V>V$llJ}A%B7BtVd_WUp zfkY4qm0^MXbCQ`L&Hp;|i$#TA9g1|c${bH2hkv~JqJZO@neQ=4_=-#K6D%CK-Giqz z|A(;L168qZ4ee|+$R;&F|6Jmy`qTzI0CF=L6HoK{Od*jFI(!ZF>}#Zta-P?7JBFKG z9gdwXvm+4@{)%4&gMXlF?qWvk$9*{!%KGomr)~CEfFns14VI zzrFIO3i(O>SuO{o*%AFcWK!sdSu*0skCq?(QblO;Kg!eqVGsanh=nHkrEha|Ys@wu zmdSTLrz6CCD}w zgGsA`-O%%#ioI(+Uz#kL(@1j$GG2^(FL3pAeZ zH3kOtz37R;Jt;q&vj|>9Bq5`dW+FNYs6c>>JxfpHt^tfK zY{K4c)C{lNhAq+gm`+2U-u!sV`u?N!i>W1p5zX!dUNnV<@8&t=c0`mPv@+i!B0WAx zGt`_8@m;q4r5&F;m*otfD_hOCJLPMD6iS0Cu{3WKmSQ*cv{LtKurrO~_e9kdzy6fFBhddF1N5QO9%rxYm|Cj~O7+ zQ(O{@pjR@d>lqNaD5cySjWPULYL?Y!x#`MezdKhn_aQM+Q@&bT#SQiPf?H5@(S^Ca zqhmTS3z_|1u42?#eGkR4GJ;MeaDQ?8x{fGSr$cIMYm3b{X?D1mvfL6qfr}_(&i!5u zCwqCLsZt1rPJ=Gc%L{U@K~OYTPc0?FI=}rbHvNLg$c^}9>!;?4dwkIZNUP>ugCay- z1r};Y8zOpD958Knvo0qkr@iFw>65$bN?0)e-lENH!jeQ=pCP^GT4iDxZKOwh|8TXi zZU;9YvhS{9fDelH!mRg|hdS;$lSa*3WPzK=iv1ZK8FK;jp?;v5^nOU;gH1_yPv%{v zWaQ8cAb6!PQ;MYfLGzsB7wa16$R;$$n!L)zzq zNua&4gX-ak1?~wU5#MR&r{kTGpq!Dk_P%s-(IDF*^ud54_G<3r#;U!POd0%BIny1HE&liAj=tFajRh$R zc@uJta|z)Or06tstt1B;$Qie?>`5SDcu5xT0R3O?+k>^s;rV^{{0uF{{1S|n>e5J$ z2kypqhinWIxlLsJHUsIOsR9}becjkLiX0Q5tmck1mSPePq$CMVhn&$H* zKAC;D!U-IWSZnTwDD$;(f%(WXSb9f47&FPjC2HWbgD5FIA7nz%csacZ0>R>=X@ z1eIgSgO{P@f^OH?0UseW9<#O-e$v`>$S9~^h!s251c|0A(*O@Mk8G+F4?ySuV2LK6 z_}T?I+j`1{nC(Nw;+M4P?RRJMZg-N^Kf?k3qJW@?7RH}z#_&h`cN4#zShggh>~9;H zTO$mgm4UXxXyysIL^R1#^b`ADOqoc41XAE+J!H7oSI|cVRIOniL40M{XE)9cicltF zvg>t_onBdAmjtM~oYa9v^;bb<#a%#b0VKHJ5%30!V4V#Dtj`C}7Fqt!ed`-3t~BiI zpW!UCj3zf|ao6{2D`CF7ui~e@J@4Ju5ns1fzXi#! zs0}*atdmO2@|-Z}ZS=l)4%LL_o{1IdNHaP{m&?JB60SK<4q{d-hSB@^%50k258|O$ z8Xu9Ut&qbg&bH3Gj&}Sfk8cv6Er#>$16hyqs-zXo#x5H~ZBb@%(2r`xV1ikrIR}2< zLaeCaynIXQ*0)$%8;blo;k49dvNh+(Y!9)%znqWrQlPqv$+-Tiv7@R z6%vEkUEi!ZqjeDG*{W@*1Jo-`XERz;#>v?Gew`_pOy%Sf;?QC??|&Ov`SB_%IGE&x zRCUo63PIMKE85J22b}W6CJpZt96a11FV)2jJ&s=eY5a6+N)Z4SOM^nQyt~U&+K_y~ z#-!FhXcn=Xrnumt;JEm}(5wk#zZX%AABC0ps5ozlpWIKS0Qd2>bCBZ=uJu|7^9=lO21|} zsBR){x&d-%d)E-q}&7SA&< zs*>8Zc!BVzebV)-D5Ub>`Ov)_X)PHr1~4#EE$l;`z6Pzj9vz^b^CRf-68n{9xPuT^j||Q0a(GAG4k~! zkp9A?WUqI6@ngpIivXVWfGDA$_!3EG%bK6fRGdYfA%dns|IG~5+vK$;EsF$LMH)&= zGL8;nq0hZR$P+3UP-bY#S(YsvSFxk(@WVo$M#Aw-Y_bLDQ7NQ|Wdu0@wrt58?vIy# zxT9MoKIe|qmupu^MmJvyB=V_tMLu4E1wk8MPyDImZA1XKT}rh+}uUX0WH@Hjf{PL0avh z>7c+M=+!9pZ6laIq5aM>?<+zB#PY1O^MiK3#0?)I3);$8L-D_Ir*Y0^m~Q1u8Tuqx zda2To+3XDKWP4loFoXsR>>xS_KX`sTYfi!RX3>Z2BkDcd9xQC})$-mVx7k<*b!VBc zCuf3BHB);u-yFEd>Ol(Q6jy0aheWStKn3@|vKZ(jq`u!*W zwEr_Bx5gTQdKLAIeHaU7i&#FlVCz-PsNxjztkT*^oVXNTD=s!hj@b5zF2d8Hn90tW z5c+1?9A;8HhutX)^DXO!e``S~Xth$Xt+^SGDDL?p)6=*s0IKU`a<31 zbPqWTQBLr=l2t68Qi0!&HWlfCeaREqJz7THQ4~t{?5b?Po&>H|ao-*;d)g84B7mVh zoC$m~Bk6}2M92>Xj@sZXK@w)aW%t$V@MPKeZ@vA(e;D%cs@$(YlEVDFKhTW{m6-d@ z^5pIw5AVB}FhSn-uS8Qy{O*kmY9jND8s-9Kzub0OQnQX2+*qm0NLXWue#{Z^{8&>3 zGFY#@&%^AO7AUn@66g-u28)!a?P&H19nX}hF+P5MKa>F?0(+UV`e&Q<h4}yY=F009j2pP^qFM?X5W`G%hJA;+$m$&iL#+{)fsS z)(pwvWH*gbZVU$^0n87hYylJ;7PI1IYp&+;YzC_QKLZopwq$B;EJe5?#xLx4uj$4L z6(Z8>eQV0Yq8!abEpFdqIhD*}<=%@gagn}z#;J_>^mexD>sm=wU%c=N+2S>YX%yFR z?=6Y944}0{kVlUuwR&aDqQXj&W#V&|CLtx9&u8ueFg#v6dVlMiHN!BcG z;&d~%o+G|oLszbQ_hUK6*+05t#sGoW$Z@SN?^P&ZeX&Ei)q2i{9?GAyVa$I`;OanE z!~bfb_soDZ(_r#MVmNs*>~SSM_xPDJKpws$Hf%)|GE1k&74Ofy7tgFaNgB?O7+kou z$kPTCPklUYD{CHN6WZ}1od^W_LSb!dQ%3ud?YG-DBX#(KXam%OX#1#fohi6!$HiMC zy&n!byKMbmYi;SeQQz}PYaij?s54ypYs`(OSJiD+cN-A9xd*7l_>KalTnhoc8D`9% zHV8!{JbnlVEh;+UY|)<$t~c1!KIo?`yeHtyEiyJnHKKvr)pGpSf-$aRP;SHSF^fQy zfr~aTWtL>rcIJXvKj!M|{qaTpglhX|%We$E@J}ADkn>rK`^?eb<#GO<+*yE9K3E)0 zaMCL&d`#(q68^X?W|?Osl_ci;_Mwl_MR((*ong}J4?ddj!`e?aPn41reC`plr@Lqq zS*$B;S`9AprNS(8mQtu9)Cu;x{4=jyGB{@|_h%$W^j%FI)CIM)?4$7W`}^*WP3~dv zG6M{u=a;ofMsP*5nm~3e5=n(-{SuWh>(%O}(;p6IW6nMrrIuEbd`m`*H`6`Gk%){H zqr}g5#Oatwk2Eo_`NPDet@~?62c%l~U3P9+yN?BoFU*xixxd-%aZDmOWKKm30)j*0 zYfeR3vgc);efKtL4rg(+@y;v*kot_w*A(5BqCK&JlG>{xzm}ldt|3(~FQnnCw5zV7Ay0<-1H~QjN`rGr_8nj`a}DE(N9o!IR-h()w-WN<7=mR(H=#3XqiE~lA>v# zn)LL;bI0rV_IurCoVKe?4Hjk78i=*1V*j!{FVyOpE=C`>t}=nx2V=(+$7-j_5rO=! zF)A?=fLp~31`@K|KD869h7J)<+|_#D>){31T)v+RWLa4Z5uFXQwos+knuoAQ%vMvl zp8uLCznu>UZ6Y-(2$!#=8Y;|<3F8uGT2pqP_z;bX53m$Ws9u6Pz(^}KLe-o5tl;uz z)qk)&B$io#jIjl+4pr_jyI(fb{+!bei9iJTUKdb4SJz6XaOv3k`E@qZK&4|3Q|)55 zD*q`u|BW#6Sf=QF2yRM6;5YSNF@zE)yIdJsTnQW$#{5@jj#k;s+bd#RtGU4|KyhK@ zr`;X?EDx6WlL#)kEJZt~+M}M_Y+_!F1b*j!u3djiI>sX`P|x{Py`JdY$rB&Va$XRa zm4(-r$P>CawNsh z^a4hhd0+c&2avu>i)4HzPt9vb)Vh=)9N|F0QM%U#)QQ;o$8S`K1Frf3`9Ax=EbmTT z3dmWu)=1=bd>fZ7G&MR26o)Spyly5_&#=pKoa!>}FCW@cRpqTV3h^tmXTTx)y*v6hf+4~AcRRPFNTdVzA^X+eo$pC$kZMBx2u;rgOY4;Oq_*Lvq z_BDUm-&|_rT2`+$P#Yre+s|rk-QLM%9i(xfCSOYagMU4=RdVz=&+YA|sx}#spqoFF z#y2IXi1n5^Ce-fv+cM;oCxPQbG?EET>NAY9jg`XA)}J_C z3ELTeFt3lFIQ190>`hh&W%gfoW^e8B?D`qyDijFU*HkFE#PMl6!$xl*?}kS9jv8vHD2Zk^*{x>`fBh(NDP^_nTSf9U#6T!$@BE;nn3=7}Mk#XbpGK{_l+1{-uYA%M`S9mxL%p z+}?2T6Qd>Ety=rH`!YLl>wB!ZA!Ee5`V?(h1HvOts2A97E71g!gl|Jb&4=&B*#Tdo zK(%4kB-(qBv_5XH5Q((3*-8SJ&!OEpe4^!(Eb>bw8ZO`VuUlD9G8DMRu76M+Yu8dw z%Fy{;KK>ZQq+Ztvm9xv%xvI#Whj~qPVCSdkc$4*Sb$tIo9?5-FS1r8h8`G<|^!K_v zF7Nz!?1&6MpV-{*yA^xnEL}dUcp(0O=2LkUC7=?$@%JrJt+khwQPrL-Bmp`Jq8z97&f_ed zjWDG`ieff-|A08jZFX;pa_V?YnYGU>`#|E8IfkgUJ#a(P`?Z(RLm#{ACaALLA6x5K zb%l*S@qX^Qcm$)l0smyC?2N?q=%z?E+}K}ly(Ykl7BUl91@=^iKM{^llNYEds4X=8 zFBHrlb%U8-poCMi%*=utjRE#lCosQNx*5ZVrud z_f>A70qVr?MIsfDdRc_+IB=?yIRo-@OFOrky}7o=+{0xusA^g48duxdKepjBwOieMgK7rwNiS&LAABOXN0C zQ~9UEg{VdSVaJw0Ae$O!m9I*>^Kz}sztS-MWn^x__(z+8(JGk)`JyP5y4Awv(d{CE z0aRVAmHWUX`%u8jd&~b0`}%!@vjx zp^C)fMgV~vGX(vEt0!z1?t;<$MbvV1&Oj0lAj z=}UC&=0zwgbohlPsi_nD>|5&r-Wu?i`*Xl>~3>g3!;{ zBMs+?iiN6qg+6+yX?{BJ#RqgA4YzI_jJG->rR8n@xS4wisxpR#zBN%pK$+O^>0lne zUSF&?oPm;IXQw!5ULgJy`ZM=i!$yE*uC{ln#AnLL#>|29kU8^TkN~2k9G$ZtE_J!Y z9|%89eN9$Q@C)pH6UEe4qL6RZrOx5Mkt6BDnd4F|%OGF&!o3qk2E3Gy!b=06utjL#{ZxG!#&foHiHgH0 zD_tGF0}K%8p)lEI%H0<;mILgIbWnc*ja=qii8>DZX7-#?jAt&%g{99a@ZPvT|5|ZV zIZ{PUtv!{i6gA$B6zo!g2i5Be6;Wvj{e}J1S^Rrcl`cCx1{=!Ogdd#n;#O%^b53wD z`WQ9nK82I?fkBzIR)1t*n2ZU-^WD|8C~h**cqM}hdYQJB1nW+hnL>>&OYGH^ES*vf zEDpV@ul`L<@Y;c(=#PIb4b9({2IY}W(w^DaN0pA|i6DX7pHGn2SSvp6a_gm7uJ6^e zFQze@u3@?(0PC((89Fqjf>7m0#aq%!wbYlZ&^@%q$h+A)RkpUYNC?YH2$O%srXH5o z{0~b9KOXoH_;nAHFUJwyKWrn&Iwr__9mkn%yn5K(+*dh2kn;U$5yL74)m-wo*+()v8Iq5XFtm^LKfJoVc2p+BkVM91)CqX0wh0^H{OUE^i0jFo z>|UwYr%$1NZz;m&W`9a|VTwf17i=co38~#pXXH0^mi0t2iWJpfvwN4Klar8yIqAQ_ zf{JHIVlzw3@-$>H2$t;mq7w4;dc|mj^RvG43n?`SlGbJ~!KNHfalwGX!NQiFWNBiz zIrnVwTck#)2DffX?NHR!AXx!9hJWN^h6a6q6rS@lq{j*s$xVi zDa0qbq%z>;@3PzE4)ji}oYPr5TmXv}a=&O_(Wia;hebnb3cucJa>!!wKYK3jo6oY|92E*Nr6B{(`EIf4n zm&b;AtsIxy3~I#P-KlgKwqjg1r-WY#&JY8#pwt>w7nOv_CO^yQ1u&Gw(6`@^^etNr$Tva#{9_G|!(4QDO~gJr1B3!OmHSi!o@0 zTWz?3H!D50Q;%cAN*O;tyTVu87Pj5U6)0STRXXb(7+r4&M0Ri$7hHy_#t-WmHtS>{ z2H_K>3u=5owp%o%mGW`DLK})k96@0TYuF2_U;?_k0g`R9as3hDAB|h1ySBU#E_B~+ zJU70kWdK)p~GT_4G6^oZ>)*2b+kKbI4jHhErs>Tx6u~hvgXk2OUQugOkIMYar>5Vvp zkRS4M8+7`2jlF8kGnLWIc{cRgcvdn~$6cb6n6mF)Kd12%F{O%3a!b77CE|8sP?=jp zwlkTKAD{8Zp%vEddnYU&S81A+q~9lp8=vXQ-kf78afq?46vX#2s8oPbvCS6UAt1CP zspoc)rW#E+N`uYKSxLXnocTn*IUIWVS^s2fE<*sS z7qTdLS2$7`N~GbJralY69(~Aj-;n8D#h--`01Y-&EAk73ps1N@>rPSf&L@eD2U4n4 zAHBaSun_&wGGkmC*EdCBGWbR@RN(76ZBHl!5EXJkPEm~XiXVFX4>d6s!|Rcu8qWw{ zl8~b})d-T8vu+{$hgb9e8<@&_pjrT2zfcn`h^M(k!WPYaU&N|&r_thBxFvDhKoJdu z!*yQxKA?Bli|-!pi~&uZMswJ@^}-^K$sS?i>9mps1Kda1cH$y+5`0je2qpaihb{&Mu)D691tf%YDv0CJ3P@UsXNk}$lMn-6N_;ly zwC(I3nV4lYJ8O7;;5xb!^M=8`=B0)cO6~FzrNe5Q>!1608@70_vLA`%pK{tH{P#*R z{N-d@!AtkS^pfwd2j~a>9?DS1-L}m8_JOgiyK1WS_ODA_TDv7XD*AF{+gQ72)%Ic1 z>MIB-%vau^ZbBCqRU+u`!xiW$pZcFk7qMC}0I6CtpU_YL2l)H%C=Gn>!3o0o!!HEG zx?I2&@3MUN4Jiu)vcyrO6=WYCHmfDBd@{iEv)Bl1Hv4!l$ip0=+qz@1Pfj2i2xI4d z(LHv&8Av=5)V6f z0z(RHjDO1AU5hL=M^Ar6(6`@8( z%}gEYQ+-qG6D3@xgUlH=kU! zUMq@|YJQ;#W+U@t)g$9^Wr-HS!zX~FLxbdmdQ>gvsntFj%UATY24MK=Nl1jUFOT&x zERle9;Ov%JD(GeFq;Lwf&SY91o#mGMpb}@S0%8=XQ`B`M`~5i@S!NiO{hk`F@vG83 z(46?smGd_jVs~JgB<{=t2`)W_zL_FoqYK$mQTbwGIf}x;06u^M7CLD}R%C{(owqdC z|9#&Rn;IS6@{_f0ZTq&X%GqnKh@~d5zW(8t#Nh6K2JWi|S-KlUW^sa+=6OiJ7EqHYD>lXy{MP5yF)Mplbb3Hh+0^F*MjxZ%Wy<;B9PY%3vm~wpc;_a@wu` z8pL1!+`GU1H}t(v53oF@QEmUq0uh^sT}b40tzt^^&t#Z^6W`MBepQ&L~)82^si$!N)WOfMCne%F~1yk;!VB&uNU^0KNs!?TbJ^M z9Y0d&&?^?Mj2oi(u`dCQ64IllSrn|MUjbfw}I6Bbjy`ZossPh0Pib#K|jpDY=C zgv797i1<(kD<{?1atALhE%uYQI3PXv-4KY`z?OcdkR31H?(QC)ntWelMc$_X1NZ1} z2K%2!IS2u(O#mW%7${D{e*Q>Aw>p!~or%M@P{ZH_4rsUf=UBjnP%H^L_Qy}D&0*t5 zc*|ZJ#*pzNqtr!dvH#re_woGe`hX40;q_zeDLq2S)cM3_%!Sg{L?LzcP7CuW8+1A? z^YFH%ghK?C(hk!rs(oRjpdc$;eT~ zi%Zn`oqy&0$_5Hi1Z2MQ3Uzy6#e{2ej{Wd!;9rd4e^stYQ0`B56n0GiXeUJ2M>7jr zd0J^6546vo!Pv5(K4s3S{4UM<+vq$4p(JEIc1S4D^j6{2qbRQpCo=V8y<-8-%fGCF z-(Tqi1YJ{LPW5(9MR#w6NNApm^HDMhwjPH2W-U|jvpxA;+B=8DQ zZ#iX^`&z_2u>SKx-a{~^N@W&bUC;^$c%l;XL#?c=79bGXR}Z$wuz$p^v3>u^e4s(o z$N7Tp0VEP^A3a1YGEWa@$WjF}ndI=7z}AL8mim+r@qaDECL+>cN{W!uFO+6`J!phX z?Ql)}^F&67eC+j@Re5g|kWhd1>)rhL)Uk08l7{ro((?JNLRl%#eErn}(1nzzo=j5V z#HqF|Kt4yC`_i?GHTG!xm5&}h2=>J5Iy+6o>(lJDb>q=J^=0O0kdt zojRAG-=5Q$ph~5RW|o3XM@*mPd_WavJjizm`r(igZdL&DH8P%%YDPf{4ytb)W)}n2F2b-dQUE8s zO3Ts;55oyt`m6n|{#j1&Oh7*6GJk%G7|wrrE_=` zmg|;B_j~jX^U0l?yT32O8Qn-s8QviC5pG_&w%PmZY_~0!pX-8yASLoKf*MnWTDnU5 zCBbCYCke|NC$jImlSxZhOJ=Ou(hZO8j3?qDkWe@5NFXRW!1gP1%?HG-Fcs*F0%&oM zf8BdYeOOO;is@H%wrzTp3R9#Eu7l~ek3e!e=k~^K<9M@dw0_@LUZs6nA=}s2bhO0G zhE6s8eW|EswRJ@1+q*~(v$@Ef#9HLV#YF%n#6vEmOX0^x6gzR72ocDg2V;wEBN8B8yQr;c#1 zLylqGUp7g>5^z*S#qv)muU43xxe*9lk_)cJbCijr*oYaE7_2%&Y(~!hPYd9219FCo zB>&a%^6@!2nz9=@Y(=CDX=>EH(Z?hV_4J^Jr2OEd;j;lnCUzol4 zrzif0EjfQ91S|L9n*7V)0>uyPJ1v1WO~j=WF1OBwrz+`>0F&?w>du>~QcAPfSe?(G zVdW6iLPF8;9AYp}zD3gx7ac_o?g!k;Dv`vqdj1+Q>Y0d>S?yjmyz^{fw{I@jt$qXx zD|@H88{r)94ygJ$BaPy?>ji7f(h0SBWoiv3$5T;v%UT;23S_w;p-6M)rg$h8xoK9n z@a=~ggG8*G{H=>)j={uuE^3(IZS3uI2v0%xj8YK?|ManpLVrL17wg88xlm}V`Exw) zGk9`@EcFLay-I6gQMrk4jI>gF@wvL%I9sh6R~%9zTA+*n@Jp=c8pfG`#vO zLUV1R#?JVO5g5L7`nk99$sVq-mRF$om1pE96df?6ei z6sn;P=9Dtunyp9CL+p7wfBoDil0hS858ws7z|OH!5OANhIpc5YK8r5D)#xPN7I6RW zQ|8j!Olxpw@gBOpQ(M4|+W)c%4;b%@S7=5ro8Ua()^U zMMV4dJ$?B2Kk>`G)6^shf!Sf@Wkb$<_1auo`hDnoiP9RE7t$*3cF7Y5hvH(7ASCZ2 zn!>(5%gj1Y`V3`wUuC)I)sv16t!`Z&>$A6Ld^47(pxYx|I6LP%9SZ2Yye{okXICW( zzNY1R+o%M*L)DZRN{(E;=b)e0!5N>tnn z89kG+LbT6vsH$DWfq>lifCEPpyyn!!Pc<_AgfiEt-%_Lm?h@}y7Bo2sh(8;Tc+}8( zG73+&1SR}(0}+^@-4To;Aa$;X1?b363284$U-&+A!GV4^zhE89Rq58Rm5 zp_`&)?&y}_pr?)cXo&^uLQR6ZTXs-1$L6C>jUl&5LY8YX!6pOQ&MhqS^=Di@V&-7` zB}nv8^gl<@AFjwdq4D7u)>)EUE55thmp^tKnP~3?}*cZ_Sc{lo_6Oj82s zJw|QO+%0#k4`M@bb(DkUBV%ca-gjw7+PCO##CZPcrD~pyL^vLh(@J(SI6Urn1ae0X znY(1_$i0lC=z8VYNp&ML=!=vX$MEgO`+Wr!ca^(A zcLF_p!E~`m^%lKG=@Ss+NYSkdWIY=CRMec)zdN4}6dDJ=B{&kuA zn+sN2?k}(8fEfluvQ0~;r*P-lQ>KgS9^7hAk4o5U()jLhS`W}(sWATo=){ns;e0wW zRkqH6C!TMF%@m$QMqV})!?wN&I!#9w7%4l7eH_o1PZgG_SPMzO{HSxel2cZ0un%Kr zPFb%!MW>nne6<%}q@={k_IMJJX&Es~5f3oIa@41W>9%j+QFF9<0|NX)+XAFFLq#@e zYTpC#Gd2YVtuL&Pyb&OZ9=t%wqy0$a&H15wr-=QD|L^5RcO^03%g2s{t{fTzU_EH^My27wP zq*7=#<4VcUP#?5MqgY*Fu|HhF|JiplDC3MAc5sk?Jopgs5ZhSkg8bz4i`#N2kPm$rY|FTfd;C7!5ybB#@0VqbFb(^+o~U59Id6d0HGJAwawn{ z#OlOphaGz!EnnwWLQ`6?HC(@S2C*cZ4{D&=6z)vFStM(&;gHau6iA4em712CO~dB@ z&0qd#w}19(V>mV*YZgx zsy!o0N!9NP_4dc56}vcE%>*?8@%9$lM4$XGUT}x>Q4ZW`WPdtbVTlNu z_Px(#gPxy6Bc@gJ_IO=gwPHr#kC^#&QA&M!R{b*KTpW+GZ$n?0JZbd#W^)nr!d~`^$o!`^>0FsTumw2PS<8WBr@;L zBf}xDWqggRIO07hrYJ*PJBi76h9(Gz9zL@iyRAXfXmE+KKXQ_L71HdTt?Sopnn6>x z)Tp;@Z>Mn=vW~s}LOG=Q!m+=x)8-E5#og9b10jEch3&=7i$^;>=9-e7*DiZ?I+7U} zrWx`x9uku!HbGyfMckug){e=p_j-~9Ig50dbFIj1YBZB#x8Hxi4t>*}m@iuTi$}73 z4(6HjPY4uPDn}H8UX#!f4ijc2*RffGc6I1cHRn+ScfH^mx{cen{yrU)d2iE3Defi8 ziDRC*xP6#J7rf!4f-Dmqt@)?@Tf18|g5Gj#0-KxH%;u1@o!KIP0(^Y&2<4flI5;oe zi|JKvG_4iTW+Vmg^HhRnezEB+kRLlxVK-9Z-n$j0Bbb=bR(btAd82#@8m7NQKj}|k zFvPkVTc5&otC~R6EEI6nr%*V6%@*1sTL8Lp!omfrb;%sDeJ^<1my%hqGOMwr{d^a{ zMkQr}no&$x?$KSver8ca-0nzwBm%9bQv=Ibo^w=byRKWOi2YrO;Vuc#MPP8gm zu5?=;=@!d9zv(XK;BOx*?Kty0p5l&pTp{2vt81g=l2V zpUyK~UT-Ih)iURNUy6ui)U^7k z{q?Lu8eRR-xFj93RhiOuVfXP;*;iVVafUayoF6fYKP+>^^6k<;{U9BM$_QHSP{^cU zOxX@w402J*vuId5w(#nRc}(!}%n<4S((FvpriT=T+~ljhDDy34-n+JZFU3Nnf4p%M22eADwI;a^Gd8 z(zdp1rrzUyo^o2S6=uSwt1Wq{4jx%lN8a6I#n&m!#h3SR>H9=4u-NG>-;snIAXuR?9sdEK=> z@+Jz~vSe$Co>ZNEdG~&C@WWABy#QXwoX6aLY4UTS?>9gz5xB$-*HrO`6@nY5xD(~C z=kI(qT?#Rf*SB)5x4OHp)}rQdM?es@Er}Y`P*+6pdSY`T=Y=mlOi&pI9?MFgh5qni z#2vx@MGT)n{8-0?Lu|Um_2egsaN|dmsj1>}4e3{V)dBaciD&bEqowbpY_PM|_tr?o zxh_8$eUTJDa+#`e)p9CdyxHPsB>R8dy=6dE>$*0)bb){fNP~2Dh;$0l2uL?mN$KuV zq`Q%pu1R-IRJyxMy1V;(SbLp)_TI1OyzlSt-<QYz|vmnS{5$Ahr22a zQN+0n`}Hx!D?m&~DaRw)JjE*e1|xTsV1Pm9<65_lG1p6omyk?TG~gA=4h>DYFl%J3 zSrHA)stM?{Z1A59O%alw7rBSq#LbWJh{kse4UMU{IHi*ilIf$v&KLe{ykuUnggI1l zo}D%=mM-BiUjcP^fxKqQz1TY;^HBpw{hJpm#p>geM7*-!yg#U`iq@0iqo zH^5-P1Lz-JMm~s7L|Mu1FFoUmj#l&wxir}+mNFJ^(h|dqPBZoB$T~erwge`Y<>-njwL1Ek8y=oY(-dO-7~V7fUkmgCk+M!#(GlA zyIhbgFa*y#X|yV7Wt*a)Fc%dI(_T*C@_^kQ;<^?LwV1XrzdZCS*DDKUlJYp3a%#`1 zHn-mXRwUJ?0~4VTmmiOp3KkXp<8U)1>waS*X$hlCf!NmL*C3J~-cJQ$3<-a?{ko*Y z=BtpqylG`Xh+)~R+K^$y4PEL}bYl=8BO@yi{Hf|)nXA5vq2Q;EIU?e7N>DuvmgyMA zNKO1uxZK5;grUu*iz>jNiT2z|ckfDOplG+E{Z(4lrPtOY_wK;g2|Be_(i%z(tgMG-)!{m{2f- zRJ~=LGDD80wT)ip=i{(lQ)-x85_;yD;u}S7$yQS{O0}rz9VBGZtrl10x_QSKR|OPu zVwdWJ6yu-0H4eVg$jim1RYJ}MDX@C3_nWj%Wy+pJGZ!SVBDWtS1Yk(GCiEGXkWEsG~nykqnqL$NMWEk zAELY?=e1{dOcI#*`l9>~F>k~w;G@xtvr|mp<5(|rf4lGM32`^$f0K5>mTIsAJCs(e zTX3mq{*X`0r}Zx4JU;{_;myuE@~ydqgp1ed=DfHD0w#k`c^z$>&J9)aQT9W;8Va~^ z+xX!+yKb3RM=uk{Y8WbQMnDuQqXU7=t3~-|p6LA$frf6Dii`F$?pPaOOEC61?2mRE zJqv6jC|jXEjUdg4W7bhfwPHgiJ#64rYF_G1vzc5wm65oUB(9&xAbD8s$_(F3P_$u@z& z!J6_S)~z0`D^cO92Jx4{EuCG2y1~e;@lOdz-f0$e)c}QN{OxKg^p(T&!%@-HbAA+9 zFIW8yEbNi7A!i8{=z)$Lle_UYhZkk5A7kbR(R&BXnUje#%6By~3SV_Rca(R%=wv$2 z+UsUfb!@fBR~H%;p5~02SYPCVpCuo4%lP0?^OH_~g7wP9_QY^L{^Tz44JC`(kD-SU zHOpw;bn4OFJ21pMzjgkHRWtJD+Yz2h$*+5Xc*85Z=7n^0 z7f0%Cj+l!T^z0Y`P)SBc^M0$A2J<=oM-U&Sa&(8QUX6MC#1H1K0S#YqC%t`ca*N^0 zY{|%<+eJVU9rp&k0lO~$E>nrI*!C!;{L$ojlTEu(IS+~2 zx@;1E0VAssW@&GQ}Fl>9X;op7En| zG8fUS$f>9=o^r=(Y0IG-4Wk_kxVK_L19vH6K6+IWP{=LsKAI!8qhdC0yhrHdl3z71**yh3=<3;i2_0IqbqoM^>wXk`w(=l;qJWq?l>)#joyy;0H$a`Fh2AzmmuehvBVg0|izM&G35bQAEIL zfikm%nL0n~Z7>Oz-DWgc@@go9*BA5d&Cv$fmCNJCHZh{L7}Nj8hx--mGC>upX*{X| zva`{Ii6ZLMyS*W+tk6O!FSe*Y6B`Z6j#u_T0NE<6AKc<#ZM- z@l|`=ZD*hpm_Db=DqXa~469iU=+P&}GjX6fK^Z^S}mDZzJ?k#F5W$gjk`qm!K#x z^BUY`8XwQP?@efs)Cd`|RFx_3fjLdOCL^jCh7P4~yNPRUxL#iY_O$MmViPY%Oz<0* zR49r;=zCN)M}xNkd{RgimrV`kW15Dsa?qK;cSmA4ORI)tWr#f45IzjK%PYA;&AP`9 zzs{@S(AhoCN33>iElM!FgAe4k$l`KNF3v@{v)P(1C0f(4r%<8-q9j7EC*6CvC+t$R zBOC_8blhTmr4wH5Fums0N~7wN65DuLt*ucc4R3i*z#h0B4bn5-KqTgzzi27T_M0`5q#r@6_0g*umk0BeXRf7!)A^JZ(`{ukINipdByD9MJ68q!Dq zyA1o+d42>v(gl#|KIOr{>h$tF+aF6Uiovp`eKJhYR-VtWcT;_Al~^)iB3yn2%a%geYvPKpxLn2NV1(Wr{P(WH#R_(_NUB;ey~ zPCT)^aj!w=dUvYL_=dIeEf~3K)>R`{yR{BAqnT>STPKy0&dq`702V^u!~Ju$pT3Ya zrbXG6OHPH^mx%h?+?oF8IjoN9RfaQihKF6AuT(kvTAzM4*ObA3fKeFTr}#VD5SZvA z`V7$}c7&{HHFkh5Ads+4>fe66 z+8Sit{g1FCgGDiU6szw43Sp{}1aJYjl$vea?gxsSip)_Hj_vG=JA7uhe`e&`0iiiU z*$dW)jy$-sFv{Y9o$HWVYYbd34Bm4*Qwr&q6C(;*d7?KGTJAP01tkCgjUD4H3nFLE z-uYsFBs4&tu-kAp++4^tt&V-fMPWlgSzL9(pWB0x%29|hV`!Wnq^|B-Geb%s3;~?^ zDGOPf)Rz8Z{__uPW_D2n74iZ`h_bB$`4qT0S1xA~O?Au@ViDZ$4=xP$E)2ppM`6BX z_Ro=}8_suLm3OyULiY1@Qs~PXaLS&qbYkaJ50Wl7plCS`|CH-mxpUhd+-BiY@eb!C zy2jU&y8UB{5?2jEo~PaSn4f7f<{B18Q^rYk?2TxqcH1Bv`$kC3>e<$S9-VpXS319J z9*;TqknYqJzh^rcYtq+cx$Cs(9brlKGI{Ui_V;Ed?%rGO!z16X@Ep__gtH@i_wN0o z%qZDsDBRR4bJJlezWdcOU@>cwxck@x&!HJ=LI8C#9Gcu&60XY@u4;nfAd>g{x%2daV^{Q5z1c_o-Uzdf9gz99sA81M= z*pXlar+eKu$So*JlpS!mI+L`xsyN|@PYocgofoMSU3D;nA=<9s1@FSwp8Y<&QUYecg@^;T zj{PW(wxyj=Udrg!aE|v?QlXEvqlMYVi5J&6@46JI)ldg}6-TLK1cWwKEgH$u#Jv@9 z`LZ{-nAz?ZIy=#fhtoqPF!eN~V(?(`I&bP#DEBUkYwn*`)eDuS21YC9Z(5Uc`Gz7| z#|X0ekB-}xl;@^7VyUQH!pt2l9o}b4?sfUu4Q0T`CNa3Bhs-+R@>aP;`=S_f-=|2k z8i(zN38yre4QlOR$cefy1S01@YMLnw?`LG*tX<1AuMB-LuB2moPI3q~ zW~+FxF*Emf{)|4+;jPSiYQ8Dr!%2QIB!XktT*g*yP|+ycamT6O^Y?3hV7DeM^?k!N zXwZTYnX>Q4+*$-rafO`#b*q?sv#@@&ShOruUXU<{QH~K(m#Cvnz3{#-g#kAdZ&o%t zh1<|C)DY87pb1;fVXYdcklMyZO<4=l3^y?M2LiC?~2YOhbQoftKe01!(rygMy7`7L6`BuNP*Y*mKLW(*;5W9 zQLC|3WLy^G^B_C(y@qR2)y`Jw2soS7a^rnZ2WBr~uSyt(S65vVtxR#xPZ;bf&RrF7iP!LSq?l&cR|y6iY3-Ycc$A`&;AwetT)z=F zo&1d6DrZ=XsWHQYtb2I%lASqJ-cihDoiW<54 z6`=T3vdmkt8}Qt(Fv}a+QkAQIc61j(8H|wDe-Rj0a-&8gdw(pE-nhJZUb+Pz{H_^+i~&a4xQs_0mz72J5}8Dx)fqLA)UbNXltmr>~G(EiEp1 zx@D~3x>y$WeF|Z5R_sca7Ax^TtZMcPXK%RabGarAjv3tyd~7Agm#U0o;mJY3eBa5P zv@`p1(2&0er>Op{+9BVlUvl=TTmq|lAOp-n>BGcabSC_sLi{Sowj3cin{`6}eS7=^3g?0?t5N8)7O42>I>{dwN5N|`M3d&R&YFXR6~s_9g{rMCE^3eo z>I5Z|T`|5j*ZS#X8$%muWXaV>dcKB&TKF@Sh7DwKcIq2RxVK%B4WI4K_+KGZIH21# zI~r8AC8y!6LKiw!@2F{Nj*P?11i7pR&-J$9i)V|T5lx03m0eeWDO^SHDAJmFa%%9Y z_v6cB#TNfPmGNfEj3&wcMkyNaDHpJT2Rfovuz<+HX+p-M?WvY5M`&zV3yGD zzA46W%3HWSDU{Z~QwoJw+STBu`Y>86KUp=+d~7jnu~KB+O>!$-Q-hc^xUC z5`xvf=-@f6Vg+VS2eFfe?=$DIG@Dhu=iD4mfL+SQ$0Ni#z0G}F4K7h7!h6^yFc=qy!* zt}fz`-+|KDd@_6Do!wb`U3FAdN)djOkY$0*oO{MPrtwcrJMF@C)inYgPpghk4dq`a zFhEu6PM3)zFXJM1(!3EK%V}AZGS;GUS+%O@qz7GU3wpOeY%xZmjg;G{(Q=_bf8Ig3 z6p{wBbbxMNoJphzX1e3DH7_(?62Bi9xS87eE9rsx$QqlA8<=WIpVN8u-iJXOUA1Vk zWM&>FAv3%F!@sAAy)GaJRvV|IOEFvoY@n;tXL!7i_sn3qM_ud0mw;hldhdy_Of6de?@oW@uFNni+GxaZ}3mGTYVIk&pU^ z`C^Z0^GmJ3QrwDKOu0+_8y9+7TgqqFH9Yp1g*{&yUXlgwS6WN^xP$p#4uIS79njI+ zfu};On_wCJgMxNB^|CcBWEP2kr4Fk|>j5XeX;L;{HwBfM1>yIt%7K}!`P%C^1uaI29xf#X4B{AjH=F(a#>foDX z-$zXgD$LfqzWf+Mc5A&`0_bh3g*$EHyh0FPrMnHZ`X>1bDpH%=Eyibi3mQoCJ6yY9 zxabmfSC@v~;QpoAx0N5eo>nWf&uCyza_lmF$Z2YOfX8Ij zmFIohRxzd>h&xqxEj3`t)4`mX_;Wv@d-BtT?#=JIu< zM?h`={^UrfGuok1fCc;|&%g7=2Z)@NV3MyAMf+Qa%>wyx2Z?a1Tkh7`Tt(Nr^Bi5t zXvc@l;W&dzh45RN>Stw%U%;55K)Lw!eznV-J}SVG3>YZFm}b7ditf{&VvGb6B^R(+ zoC#NG&AGq7Z!@LM#{TyD{#INsU(CBgmPBH6WjX~%ZnN)#Ny}+)GH@gXdt}vNiZsqO zhB1=z4^|0S>t4uNDMyiHm=X{~IEHT2wbAK_+rjqwxh&@8mW6Icb=V#5eT@l*DA49Q zEQ^ZE#){_ShQCs{M9uH$J?IQ4QAVG{#*TR|2ZJEDjP{exu4iFd+u>~c97x!3Afvf% z+w)>PcA`_1Ml5NSA4XJhE^goAvb0am+SPnE!kp?tU}An1oSrP24RqucZRL%XHY1!@ zzcL3@*yl=8vcdIijX66GOiv6k`A?iYX)^ES#dwo|X<{>iU9CErqaT&c>H+Z~TjJ$l zXBUMiN}vZ53VAAOC3Lc??r zvHlE?O}8#=Y@kwz(`^mO^y1)l%eKSv%cfp$kzgj*rM@1sLfYrdsnA)f>lHz*hvI2) zcelt|x*56g;WWbl^-i*zAzj~TtxtcnQQW$wofVmK$5*MIujWMQyh-Q zoXKJg%?{ibM`K68e%u?$e8#4F6+x^*v#cPe3k%^QdU(shAmtV3@Z|{u;au?Qk9OdaQJ13KPZ2~a%u7TCccH@2* zw>5|VN8PZnRQ?tnSBdi!9hsg5U;YH8l(B`0eUZI!4;YLqId&#`+h|dFChSkbAQGb0 zb(6Fc!#k%FcWs&Ghlt#45uEs2)9p{+EoiJ8fKgYxY^1fl~rpl~*av;#F5z06~vC6``j;jZynqZN`LR?AmTowvC5>oyv7cFe63=t_zE zmxDS({0*L=-$!X}(ia)qAYMZ(|5-y{fp!@oJOYgd6!YBr79lkA!bi(4xdDaU4%Xja z1u9vBcTx;V712U?Z^_7haE=G1I`0U(=5%G!U!(7!?gSA`IAAEW;)6q`VgkJxBKIbM zlDqjc$tB=V=1%A#x}Ap`;quwo#GB9!+%`CoeAB;~=eAo5nT{EV=EDX4ma>N6c0AK; zwh8t)YhO?1$;f}ZiJ@F?paAT!34=ehj0xBPgNsrm`<0ECr&A=$QnN*YGaN{JoMe5c zJJ-YwmyIS}>5_}yqs?k=hJj*mV*T@l~$5My;|$!RsT5SL8{@+B=EPE3y-%`(CON5uR`R^D(*zu(0AJW*rVv|!K{ncE9XViSt=Vk!x$9@&C{D%T$c;gD zeWxp3HEd*&f|bO-90o%3WnyXj?M%@zaN`W6ak(t-kM~^@uQqGijn&N3=`j5jC9PSf za$jOB@OOa;4`|GEJdSEWs5yoKPR2FwjMtd~bSgJ?pgshDwIPCL+gYOV=1`||u6|(R z+%MDC&9x8UzWg20f}1@z`z9&Rg)A@uL(HOvL>le)N|gMd@4Gm1ViFQV8y}jmzD(^z zedKJQK61i)S&OjRA4`$ohFdt`;w35Ho$xAA+oHCvyJsvXJkx$p^i7mFGO6NE6$k+0 zGr^QGn9F|-8J94&*}iMZyeq4!{`44nd9v-^q58*kjb{q5F!~j1UC?GWpwv{)q49@J zXwlRO7iTF^Y)6vW!+_n2!zw2#ac*6suP6E>Tu3u5)nn^rtV7A8%+1!o=&f1P=0h>= z)$qf31s^4qDPCRwr18UEa_notp}IFR1?n(%4RpDLksw2F+%&UZ)v$tso+VY~+4zlL zmVe52*g1?I`voA*IBxB;7ijz^!KT+GE?l~N>y85iZWa;7TB6i^QsJZeMHD@ z|9AkcOJ`|wJ(PGpDI>Fd1X#s>oXkTzHQX8xoC`fNy3xQ14=5d;ca0atI#1s}-OU>Y zl@31su@tM-FjpQVKGo|A$-(7xT9*AXx@5FYsC`7hVK0LV;w87rWlD%l=R}D_HwNDz zWTa|zZeC@klB>$3TN-Quh+RB0f(TwG2s#K>56xx?zf?)bnJ@Co(fp40e|FI7wygZB z3gHvZ(4aJ9Nj^GFG1}wOr+J&pG~%2voVIJ&uj zH&#CEuc!!O=3mEC1N`<62`w8fhx>H8XZzTHlBZ2V#je6LFlprRYn19kmb%~0Gx;K~ zstxSCvVKOy=P8oXpoxrvKPXoBSrvoVMgvs)`;v|CX;Lzvm7IRC*7^u^z$bhH>KOy|?0*vVA*0O6vk(C$nR; zucDF&^qGbeHrqtlN_C{}jf=OvW;DpOXZYH!qSz;0@%ta1h0!4{U#f)`HW~0P+;EQxufaT6J=E zSn|bz8=IsJ1TlAo+ol{IuV0e_2X9nt7S*q<-R}lGDMCBcce!AQxho%FVa*}r$=&Eg zvYCFT%Db=rcCpDUIte`4meNO1R980wz9Grx4&qmt6u~Zk8}^Lt1Fo|a#Dh!5xKA&| zO;QWd6qKPa=R**2uW*<&tR1%CZH0mEv1_ZE-w>VlccG*~)%T4Q^3 zH>lgqv6Df?;4JsHoY^>SZ;sQdRIKS}%T`)FIqF=0p1$J`b>u`77Rgts>;5Y_tc=G~7{?>Gzl^smk+O%%AxZ z#P#92&YULE=M8C|*Re()(@l*3qd0S%ckV(eE`kM%PCMZ0)I7Hp5rFG7 zWifgzFboFrrh3>Lv0yCa$8q!Kml7Rxs+FvOC!m@Q>0n+Lyt*(0jhYLSu3OmL!ss`k zqaF4MhrNF^rs6OI-YK3YL90vgSJgX;U$}PkC1+br9>v30B$z%)f^7Ix#8ZP9KOvM^ zCukXR7;d*wAZPWsXUz;qrN7RYsI*G&*g};W*0vh&f7$HhFm#2+)>|LT-(GLie)e(a zZ_UVj^rk(*s$%ug#4`~Q5oLS|@}DYhC~o%@EgTs<6t}=Dtp!k#>`j{5ljZuW#29)0 z%bF;88tzzpp^^ob8#~M8P#OgrT1MH}q#-DvJR?UhylsyKsRTe zLGc|*V)j}#iLxpew00%~cTF=4$Wmt+v?=q9{fT#N;rtUt<292%HUHvvY*mzmP-5D& z@mOylwDV_^!~n^{i^+2xs#)|N-la2{0bdLWtKwiugOdxBUcEFqQ^Gcs`EQ;nw47%c zw^se`Nj4uuZEZ8e;B2)RyHQ>U(j^oMC1gH1+55&A+Ue)g{9`94j45nmu*BGnUbXUh zkyf)WwQNc{3qi-hf%VqZszt0W^E7g9>#b~EdA749uk;s*x6(#Ka-uRV^V!zHEd(uea81h2VkLP(VA>{ISC%quvZAdN z9dfc~^UP{FSPr`vIKkXe2jD-uIh5O;D?$mJVa3PPSNDPKHm7h}(4B6y8`5g!_|&HY zUOt$Xu<5qlT2T%Ru;tqC^F@J`T2$|9{w3`VzX!n-RAjNHswJ&Udkmvy->?084CaIl zV9~JjFW_ASn0vO9CM?Ux-$)_GGnKFpXD2K4G@MYo$k!nB)u*{ABSUo9sKsB#R5@dE zDK!C_t%-4{bbv(S@R0m6w%2z)8bZ6=1NKE~5H&SA_^ye6h5yxu+&Noyx}__&m1DJv z9HJyuPeIQuBdtBxKr0DmAssjSjVHCy#1Bm}u@<(&@7)LTn2u<&Vs)m)Gksy_36kk*d~i7md~)*L z!(lWhuc-<_ElH66O@mN42ULE5!{e>a?OFp*r8g5pz-sj5q&h_oUt|DIK<)H9r&xb= zyUn(}Gm$rMVorJcuw zQ*CumhVdw9z7yyrXy$YZ=>^6%(5oIX;iLdsI+T@Gv6l37zM#gD*ibo*#NN6T`Rsad)@Ifb_Zp{2gjAgrWbX`>H@y~ z9;Q=1Bq4YewQO7uXGSWne>lzHM^FH7fm-3~@bZtWq@ zA?(c4QmWTQK2UYXaBOw7#E%_r0I@&?82WF`WEPnQt}>{NKp{9o*f_+x9~i7uXL?n< z#$}-$z94HgT{2zS!kgAl%y7q;0OHU#D?{cK)8Bh++L zm2M?~=jOV**yVS~rQ9(!w3SzJ8}Ch&D=^_i-p!P0S=b^xc|uo6T*B_ASjXjH{8?h6 z&wZ5KTOG&ZNohBwpD#vpA4D3EZIy1*fjxur{oQmoQAXCa-IS0y`kvPsc~;Ky9dJJDAC=89FuO{uwd-1h4LjZ!4+OWBEp*-9bAO3j;ZAUeI^z56erczjeoU z7s!;0UzoL-663l}vgvcH6e)%+B%mpfquoF?T4HF2bh)XJJUns(JGIt^-tt?DItw`c zP{f(DP_R=-P;A?TxtSha*Cb-du63m-^}Pa$KvuPu;W`|$u38d~J$-)@C=8dftm0Gy zvALm8nO*|z$T{B0$ld*M)4%`#Crv;{$kt$6$8Z0hi{reDZ({^sh!+~48LYXnvHMVh zA87;b7MkF@c+nl^#n!-)6Q+B~4?-maqNI>wb1AA{^Iiv~u1->fUdXv3-WofCl{A5q z0@Q059%rlam0u>z;#nZn=+mmS5b2_cR?SRxcOt-^!FmJlWR{N67EC`B54BsFbN#eU zw$FK)?i)Z<0i4a2bnsg+Xh55K_x1etrT<5x{;>Y`7ck^iY6&6Hm(2_OK$!)CmFi!h zN$*0<+9BU^M}l371Bw3xDA}d_`meHt*1i1UJKF4JfEwF=rZNxj--!GFRFThmK6Vn} zrmBiTak6R2&@t((>e-2thkH{#l->gAxz8SGzLN4f*_f<6q=C z^?vGs&tC?ZCrV8=t$)Hu9xYO$n4(UYt@-(SsjK9KugdJlt!R8?Os&lDwsG(nq0Qoh zUPWq2;rY3Qhcg9l5ZAo@b72Wp9F@(Rfnq>eN^oEm$69$smdq(kzvVPPLmeZj9r zl|?Yis)bq%IVoLv4%e>++DTs!Jw^GmzOXD0Y(1%~e&=|$u|u3L^+c zm$WkDVmB{s%|%U#CcxZ?E!Rb@c`jF^o)@|BA=04bc4f1|6mxohDTWdt_|4^SGdn(O zN8i!K72A7ZlnwoDOx9^ZaS=T(E_no1npO;fP&9MAd)!Lx^kpmn+3d>VIyu8nVB(n zkeH`=fa?I&W?UgOvt*y$X7^4;Se1Ko_g{Iex0 z@CRIz?WHo`G!4GAEw{TIyQ4_fg;st2qVDwd9}Ze#<+ApLkMZn0eV?uws{z2ahVSYM zc+jDozupri*Cbs6H!qc;m^w?gb@~fWGM`tZ=lT`%-LVyp165#M@A;}WXJ)J7S9SJ5 z;pw9RL$70&#B~Lr2zl4cUR}fdZMk0m+j5uRkLaDFV*7l(D+Y9jKwo3_U)OFBU+TiYTgm1q50m}c6uG$X)hCeHL zNmKbs3l(QWGUBi~f+?y`>jIZUFaGsX@j#@t^pc-(pEOJJppR*(FMS1~#1)_*N^ETJ z8xf43QNnmGf+G=gJ(p|_;TpcUC5wf9pe9?O!|}Xjb3)RBTuj_-7_942t>m|(Rb1zG z`&lcH2Y$21MNWh*xpp@0nhcUZMwde&eyPwW44flFLY)=hlJ%zK;XY};HN7W9uWLGO zKW1!a(l;NKAw=JB|B;cODIq`j6}?G??P2s4V>9xV=~Eywb(wd@mjKzY^3`0(`t6V7 z;35uYB8jm1Fd92z01f3$RfNA-_CPEDzY2gwv^RyxRs)X82_x)N$Hk5%d{T*yf)ds7v0oK85UC?n^2+Yb7ft9Fd@- z1)r*FD0Lq@MDSS5oXwq#)l`@>GIe_tnvn&n#a}z3>k9R#^SkeGYtitvZeJCd&U&(Y zS_gqY2K~p}D~qw%yRgh}U!<$6PP3x{l=9n7WQE&;Rtp4C#T>}4e_l$kjZ6*C*PkXu z|7mU02AajLd6l#ihdU<)_gQKGhnLSkG>HCl9giL3e|Z7?yD7-u3G=l$^>Zw&x4;bb zT^uKKnFEPUqlw$jDh{g~peE*|$jKjdUl zLqpk4xK8HCxY_a4ypHCUlirQeYR5MsKtCC2UeWcYjv-gwBBG2g>1r4Z!~OPBcQ-vY zXO=vBlFI0gBYG+7oSAuBPWe5TVpi*~q9ad`%c@EB6zMtwqjQS+S4|r zOP#U3(1+PvKNk{>uql{ebJAl*LPb^87uD7vUgUt)LS?&1Z%wya)1_Cow-Dg(=)VAj zr+MwU+Ud)1j&L(HGyD%am4h3%j(p!N<*+ zyo=MjV2AAJu5lQR_ixY5?|wKd{9uNs>aA%k25N(Uc;Th~?S*HoU_RrXXW_t;S>DhsmU=&Gx2VWjC3GuE6o86Qym@eogQ56V%;VrUW`8Y z(&o)R7?lDx(U3@gz~jHJBLe54gT~Q9G5p`%kp9)a=pg9dtm;N+sOP_F0J)_Va$NaX zzG&{D^*@}pNdIrsl;7O5Uu2JLI@Ex;v_PZi@oIlcugYi$Eb_6vr_lc!WMDqPTJ?yuW|Nr{o0nLMdloY1R z?`xIc9rb_Si!7uEG&ImigzSIwdHRnl{jaY#4~7$7!=k#sd4B)AwEy9QiT8u&Qbxl6 zZjJS?r|n-pdp-E5rLjli{y#tIf8K=upFZL&8DM_~KNw07n5R`w8R1GFn|iIWl^zoH z&nNxqlm27X3BCW*a7x@y^_Ugo7aSZL_vio9v})j&=(}4!Veo442@?(c{&aqRCi)Gm zU5*iO9#3ifW-^IFRFWhO>k7`NxZAMAS-jPw9jxK?sxdf*Zbar75bEtTtb4bfg0d zdklNz>hUn6RfEE8b8H_MkKfd93=Juaf;LSV`jN~d5Gc1wE(Lg(3!9 z+YZMv99CN1u3@=6VLL_pTy7{GZIS^;YgJD&Zek+%(hagTIc;}NgmpZ;fqqTrpw z&QYw{1N?zEVYM!mDM6Bzo8}q=uk%;i)dQBftv5w9@zxF*c%mdR1tmn_a*-E`_{IQ;(?sGvFjE;5@@hZyu zlhZ@M3?oqdpW>D}WkeJ~AYj*Jr|M`GM!hv+5V+s1r7|s7=dT&kby=iyJ@{O2{%GSF z3Le{h`|w*$oCoLmznqwoBL8+`vguiJ6sslLb#2(nNP7h~EaLp~9r{VtvkAoGn?3Di zjzwFeOI7Q%WMrS={YE}6+mNS<|hjIRUmazrGgb!rjtb{Z`k=tj4`mXh z4LiUI4cIzb zWnh#xQ0mU7HHayT4vJWa%m8M{5~a)OzYn;;%_eI_Jfk+BV6-IKo5%5|Jy=;T5%eh( z=rQ^swO@CHY(~nhrN2Blp{@6)OpqwG9XeKR??R#K%sq)5$aS_t=wLpqbt}!vxnOC0 zdB8XU$5s!ld&8tAn8F%F{@C5q5R&XV4Kt>aS|}q+mR{IRQmit2cTemJXo0aFg^nJC zPjgptDma>F&E}2_3sfqe0>C4d*WM+(7}87~9}TbOLK*B%z@lzwO%NPI9`0>TFIIXb zFk-WRuFqgO^|5E1h~06?zIlCr;MuG0$wQio%CEQ}Pn3;d6n73$V(SH5el$m z>H@db%<4v~(=-2rbh~S=d_7;bO8s@+7d(zZR`B(I=HLGKg^D$^EFr?Z`m&ra>it&h z2P{HD(F#u;NxKV(DJs6n+E3?XP}cD3GRL1^`Di462}a_R5qgybf_^ zMBJi)-j=7SH7EHD6%_=qAfOd1Kp6zKN^}jw!^0~@{)DG>Dkj82PxbBZ(9QtV%)$H-*H80>FEeCHcITOWaVO=`}Z*!uX5q%~qJosu`Gt1@J4)IKi2-W2pMwSNQRkYQk} zFC7^8SD=yTO3?+rOWeLjs46(QK2@l~!k?8N_N&<}+`YjfAdmrR=Uu`uAYsmHV|5r# zR=EkK2sp{n*to=0f@)fE=gpfpbr#`6krXr+xYzL(!!ErfAdghB?J;5oSo^cze+=K+SJ_C6y3sH!c@cMoLdQMCz~>GYCO=8WzydE{q@QVK8?b*BG(TLL4@b| zdBl{@<@(yro{WDOo9@#SVrPH$*m4%@deq~B25gN%k zZ9Cf6gP?eWe$<1lCTv;c`UIN`Lyk%=87>H`8)5T+2N9_bA^s5G`Di8ZPO+h*&TjSj zt#^CIHQJ^|E&LBMr)MLW(INP^t)LZVA(+n{y|^(RNEORMLac>P4=0{liDj2k?2KoL z4`{~5gFD~n+iNL4k@EOZTq*BWl(4K!rz9e{K01f!F`H1i^r4-`;+nEA|w<>;h zO|FNY6B)~%t|XAw{QOap8n`o6{;R1o==i;Rtvl5pkF5}9)cY&da4U|J6SlihMu>JY zaanX;5jKPzeS=BeCe0Hqbs+34)U@)y!SU2ammA_1_LxOmNFx^VB*&Zm=_KkF=N+;| zzI<~q_v#hQP4OGg!jv-vDk&V`ADer=xcw-uPIrx}rZ_Bm-Z?i?|7*oLjfO;pR)LnM zdr8(|3(W@+#C9h$uN(g7IA@c}IH~;SVeKM%$>d_T7tiM`aamN~jT){w$sB07lbvnW zxnGSQX}l>A|bkZyrHq_hd&t+-APq9ko2ZSz|GVYueaRm!p6o)@toIa;OSxefLuJmQh6Y+lF0^ z`$S(H6J|2Ei|+Mo#&}n(jcQo1ebbISd+MFlh#ZrQMm5f%X~)=E!E!cXaA%Q`;ElQJ z9}B*Je%bpzw48g=rcTr_<3x)&)!f&(u7%x=aR1^Fj@+%;M%{br?{HuLz!tHY7tTY0MAcVGQo4(mswyNSlHZ_ac$86KIX=9l;HuZx?e>8_Se=4u!Mwm-hq zLAk&AA`%-Mzt}-l_-jue1=OfQ*BNOn^oJk=A(t9mWTX$_gZDMsQkcT`6>bqUelB` zeFE%{8FH9owji5`y{6wsW=ai7;Kt>xv3f!kggqRD$8F9wi~96Q71J?pkq_9FVP-O# zQl2gIx}#cFO!=M+69t|T34_w7T#|!bS%(iKz+yRq`&J@42G4B^7O*E(zRyg$7C3JS>D3s4 z+zs-@CVEXXI}$&8m+l#Mz8jd-6EK%ZylddXPMi`xK8PD zcyqdZIHzV>R#~3bKDw&K18c)wbDcuFwZ{@$9)Ueji<0>Xdkf!r(AXG_ZOOjA9nkHJ=A(JGHKUNM3p4%G zd=Az@pWm>Y#WioTg15GF+g0#W4t3tE1Y@-D6g^8i_9s16liJRV7o2L*6ci~KiqXh> za8c*PiqlPEjTb9p0|+VD-k#I(=6mx^XdX{OGij|I1+o*^Jz(P{Aa1goTHD`Cu^(+G zE-be9y*|aT5E2W*InP?w%FNAGZZ}TGx79XZw?7X#O^#zar&y!2tCr<5CE*0(5?#86Y(KD(< zS6Uu>PBl$i&UC(E7Fz5NsW~76m53*Be@CcC52a@Bgh=9cX5J0@-!il3W+LKb2@CCUT$XL&Kp+Vg+=3I_B|(F` zyG<-uu;4BecXtWy?(XjH!QI^*?whsm{r2AH?)9DX+`s(jm)YG_T{UXdsJoX%V9Fg$ zbU;bz}tG2*z%y*Eie3x>2J4;GH3 z@cTTZD_>sqy2hALcZM0+`wzEOEZbUbB;twTM;XD^zt~B6&7R4We>^rmh{jc9trOSk zlU8ThQ2v8&mK(%c7oa$8sKlLjwareOFE5Kp?TmnBss}@1Jekdq?%>iNq-;QeBvYv+ z)TVfQE=IDE%HIm@fCxUCm&|4!= zimtzGuy921)vBZDAYmb2udKnzR~6aWN-ksi{>q;aES#Pvtzq2*3} z<0D4=T!B0zV<6Qxy2CD>^)Bl$dV|=mFlNH*Q%k*OODlha{CKX=XQHsoJ4|}5Ag7=m zZcuaHL-Yq3?kV9;N)(k^vw1A47;4JO-hKH1S^3A@N|c-WO?;E-@l>O0vaE8qulieP zH3!O%7tf&wt5vH;)K>oFo7oR;JEd3}VLGELX~mzm*4CEmR=sw#-E(u&9fX|Y$qyOi z(|O~*lW*7<>L*U(jJ0G{oZ+C^MqSHXbA_ZH?#f|MXH>O5s#RT(%;$UIIB|WI9T8|9 zl6%TbQ!(Y&&-z9Y3NL*7`YtTqc%sqlQ#FaWryCm+g3zS3WZc}t-Cg@Qgl0)Es4`s` z%=I3W9K$0&-sa!Wl7C&-=P`Jp9~NV0=Rkcwap`pT+xQMrNCC~r4X)3@_4<+sY@csa6Uu9-_p8d#e4(l(q%hR=r(SHV{ZZLb*(c$vPf3r5C)7?zYYniU`IpU&ga*&aG$yCKo~ znI?^Ubp0en+xB8NVgU|7XJ?wS8n{ENf*;%H`ZzxGqxyIYav<^a+TM^ugQFQq@h$8+70lJ12z9Gh~OqmP2KBcslha&ASL%w&3Ht}ox8QXKG zBpXIP7{Nvr!JRiL?imq!DM1L(#s%L8?>zb8D<8++c5-RnQ4@V=fHRdX<8bxZV1;!a zV*PPa)J%W1%?g7jDp{#_>oy`3Ssb!};ed%vStfG`rlE14;N~KCdHI&FCX>MwAkqVy zvtRA#M-lqzhcrG}w_(!wr~bm4wxYc5)<)F~Oz8<-D?mge9jVcj#1yja>4C?V)=D^F zW=|`E;k@eMYqu_)+7%X;4htRRN)463h4T{|E+$iShD6A*5yYm<2>K6h+xfXxJJbpz zk>FuqDv}7R;Y26ipd>eab;9-XlfkcGiAn~3xarEY$Q+;5x+fi_)-3P(Nx+dQo+4h? zC>SAoVTf-NGc?d;`#wDFJ$QE}J4vjcO`z&(zhnqabbvV_8W=uzpj#0Sp=_Z9q-MyI z!ex<`H}c0g-VfR(u$iUC4)vDewfaVp2CZQ^y?+OvLQ#dTeqf2tlkX$!H~k0~~zz@s1MraA_jF!H|YjN~iinoy<4naHSFbHHd$2qXyvy!TChA{F~98;s|Y=l8*hy z=CA%POgB!O;6CZgY9k9S)GvnQV!yC zma==KDWiu7qhgX7n>V}8&Qwb5P625B*`YSb?>HMw%*db|Gs^IaCZBm$6qgaO=tz9)3lxGY7gre3812hoTF=aK0bNa;^-sa|o zJG%-3PPv&xtpf z#jJ7Z#hMo#)*PvcoG;7Tc;Onig`5DD(-!k0`_$Ca+|UDK9+Ct`!-{gR0_9Qow=%n; zHQyX=NO_!BNj*n$hF&(nfjh>|rA!;mHxR9GuBsy!*c(t`$WwALMSxG70AE#uQ%8$Z zUAcn*jkX8PTY`Ga{h)FTkyG))ZA0DTP^+8AL_Hs9?MhzBIQi?2DIl*T4MpA>bz%fj zDlFR^3{^Y)EIf6*OSLHb85Yh3c)lR6Sq+r`kVgLH{8q3aTPc1UgoBoPSm{ckEtHl`L{@$gD|O%IC}*BWBj{wd+nwi)yJI zsZNRwiP`9@^(3-17zZUZ8SM{s2jePf-Rl)J0^07^!9NCh>9;c2Kk z+9Kvvlpr`jFA=8I_@W?R#oye!hq%!m#bIyA(wb8QGD?8L;{)u+3F)MUvWwPnp_^_| zTWPc4rp^LDep}%5q=b@FV~@+b)C(gRe2sX%H&YUegcw1Z4;WG~G=5?)>N=X*6LFC) zTK^M1h~qqHjV!eB8Hj`W_Je3e$G%py8)jf0nQ@(j_2!^5oM^;H9_rCFC$e_}us3N14 zq&ws35-hw|q$A>0+=!6|k%T-+P&zMMsUEpn_&(UB+S`j_he$#R$UkPgElkwX=E`8w zT%TG4CYyw>s_C6<+|pqeP!XmYuJ*W3kAnrg^ZdMHKo7Ou;1h84k}1| zl*Yh4*l@2bW_IdzWefL$1QS{b)rF1tVudaxMJ6#^jH!`px|P?; z=KGIn#cI=6H#Vn6G}#Xc$lRhV!d^IoUBDdC#7FINUaj+a|CPfw^>X-+1Qh}0`&?BT zi72wIl8jr=;|I?k2V`BfK`mpK_OZuN|7nt36CARA2x)+^ZJlwjv7}&TTA%9<2JgV4<`j21|;04Zlkz`BU zA|$G*Rh|(RfyatkKsN8I?pe2Oun>ol*_y;C z{`8PktUKXV2yddTRb>uW{JLz<2+Fmy!@*qGEgn74jrz>0z|~ZZg3*4pso7Xve9S`o2`htfbre9w>=TW8S~rp@RGtK!}dy}?E^MbDuFh2pXfjgyjU@)CDO3N!|r z0zR|yh7qPm1+714=fzv3r}YIf(B4q;6V;OB{%L$3cwp+@^MGm>KTAjlte%WlGVO5Y+<*r;;ITPI9bewBeJ;o?K~Vcy!tO7*<8* zc$r8z1kyJfrPvyYhSQ|7N6Nd3!{~6pMxUs z>ubl7<+c^#$+{zIcx_{^ z1?G0Yvx0sCuLZ1=>Ngl)ustBlxZ2XUsHp12!WiH`>=M^5>>B;k!b-EoBv(EeroiNV zGXrvr19uXVH1sNULvUa0i`gmH>Q7BJ&EY>bHC(ZsocNyr+P`cH#c1(@dfktniVAjN z-78uHZIU%X!xGG@SA8e1PETTVZ6=VfQ9<)^@kB)TEp$S=5HGqY{Paf%n$we)`Bg0? z-S{xgfeAMDQG|M~2747u9rb*b8jT*HGdVoi65B$*O3B&aSn|p=J5}3qd!lYsvd=81 z_nevDiv8@IiJtVN-$V9;Mmn+w=Rv4gE?>6vOSS2YYVxFdyBwy7F~sF0+bP8bFR(CI=Jiv7lTDi@;^uU9Iy=!b>#s^)qO0VpJq zVNh4>CthQPs{aYi5ww#2-Q`71xy>9mZNV10=Set+9YJBBRW($ZeJ}iJ=xokpm$o~B zA9S#aNggV>G)?hvq-glX@33b}%tq>Ja>r>p+e%cGh2895^&hlgHL=}DqY(PEi?tEU zYcPb!+l(jQl(Z|M!5wfthR;@lfeasQO^rrOiuaaVSlLNmw;^4o&s$j1lefB*q-)JB zg~*}wNckkqXYI}MRgQQfziOp;;9}|CjsiU{VFl#1#OFE|CU+O$Kqu7!td_e+V#s5w zH4m)vc=_=>ffJl2?U`lyh`pYs9cq?p0aSc~rX@^5z+jR`kp^mDz>sdTd_7AI`yb z%iLzPISmRQ1wR~YFf*Q-LorZ+57H^V9fZw#!4sSHUgC*16?fsQi4ejdervu|89z>m^KSW&5&P<>8EjzUX;qwK!ef}|emV%S>qJb3d zhZ!d#N@?~n$=8s$IhPCe_SlN?#4}GS%K1HL)=CKZ>fNv+Zk3ZZnz5s}2PKnLMcuGG z2ZTyU;aM5~uuA)Z{SZ%(h{LXEm0fMT^vYX~Rrfs-9L%KyJ3yVyZ#Z$%`}I1>2*%dW zUVJB`Eu3+goB6V^DJ@)AuT^Q)?wW^qVr?U=u2)UuPkg9?M?cVVliGrjQM!Op^;mj^ z=g{W>s59F#ig@@cF!sgS{P@30BmYR~+i@h)Wo~PYa(U<@h6;&3m$b#qv4Y$RN-(9b z4;DTJp^efu+v7dMa@TC zL6UY_U;!az`|`m)JnS7V9u>XooROD=$r}y|4Z%?T^`)3xBdebyIwz?}nRu%?E<3J%9&sOWm(2_cC7v(|J5tlZYD|f;(!c(>6O*yQJ>VvW z!K0tN0*a;RA0RLU%g+W^o@ax@3W=5vH4y84Pb5k-`dI=q|Fu) zMJ1&;czK_0Y#c+Ce2{sJ%Da54Bp3vJm8n@0)rg0}9miK8;_1rnD^v;;Hwg^}w;B~o z;qorFM?z=47fkQjbg6c&#Ftu5jRY_QLBj7i85#YHdVb?PHlMd<#pzWOsE-K-Z4K!3 zZe?V`7LJchIgQR7zP~;uSu)mYaK;msDX+h$jo_ibI-FOYHk));@ot z?Zv1=E*koF1=vCD*s@dX!SU#B*6#$1k>*2Zn`AoxF%ote35&OtNrpDu^rwDaoN#Ha zaz49HAZ_m~NZ1}~VE)&F;Xkb6ua{~ql!U>SSEWDkGj+0XMR!ah9B!=~9S3k|Oo;xN zhtjFv?C^6(wDNm6)Q9tYwyX>0RnjtxYA))-xl3bs`BQONA=j<$P}lhbr1yQrZ(3dR zs&6pLxXjeE#!TT<)b-VJ%yuCZW0nbpl&$30gks%_T#fTzIygJU!&;G9EuaITMP3hQ zXI+MuR77rtX#0-hcW!8;NyVa|b|i6#Dxd5|PR}0WjBf2r*hg=c(WPT)l(W;3q6Zuo{Cs`8E|hSN8kOXx2A}I^ zxz*^OXNPrBH1%ZIw#CzwHSOG3-LY}MxoQ-Bq>Te0Ybo5JUC*TpxfhY}G(R;X#sIHs zI?^8GoHO?kmAm$(#A^7qgcazfVpW z&>R?~Pzp+xe=Pe|SM`jnXQ7aR@;Qu`90^VcUaLjvCAHKR5mPI%<;-@i)CiwbyDbL9eRaS| zsl*}gL#foCMd&FXI|oO3p*d=`TH`IFsSD~bp*n>oI&ivNdi=T2n81ZDu4^fAH6OV` z$ojT*Exdcw({Q;o)SL`MK#{`C0tD3>4X>4>4`zjmlo<+(KaA9<@?Ow$dlZ3eO!KLDyEMQgoVYO1mHu%-Wsms@r^$q)f5|SZd2o?=XJSBe=av8D>@g2 z)caxVzX;#b##`xl9!P5szFv+= zRQ1VK`BaYz5)+~KI~PF^IE$weY^B6TdUzC1M~g-ifk~UXQT)Ny?+%6<&va@WelE=i zbd$ZX9Y(MGOlvu?J)K5LtMOu)Z4Q}~!RJKk?~K%9%LyylBm=*XB-8K*(G)?O^f)fW ze@h|AC%)OiFNrF%SF971Nfm6T!|kHeZv9y2ecImQa@&2E#3(M;3h*-6aby(I1i?MYOQN5nUQS|Ll^etWuZWdV0 zkNi~N$6ppx7!4o_)N>LYAqzY+hWq{mDr{(Lg<~~T$(7Z5G^2AcKG5B5oKVrzM^c2UfpJ1Xx9k+{iA6}gU;}(9TZ$ z;JaGdPhINh;#rRayjU#GzXGD&lNrx?aVdwa9jH$`c=E5z!%x=IxaxRR`X8-sSKjV1 zTNW#44+{Y5JHn4ZEC&=(infaIs?x%z8JTi-)T>S#FMl{0{_~}HaM86rDn6~BY)fg` z#fcrWst^EzsEV#c37x+UqJ+|CSP?vF6xkl-)zPi@d=+SafdiG(2iH5z&_I`1kn~!D z4a5EOnRK+NBfmHxT~SEAQ1eDRj!xqPe09NmBou@?5YQGMfHZ1xF?LX3$f6bLI*~~a zBAMHT?+E8P5-!3@6Yy4C$SdK9D`oK7Ecv9FhCOS{my%s`qxgs5C4<{6vMD1;d2g`@idLmhyBNT1c+qa zuKJa6`Abv%5Gz#~ZIpmM6KL0H*?tinJZ|;FCCA$C9{ZQ>E8J2H(^EG9-xs0nGvcXD zg#lt};O(fIn{s7z=oLNJvR@mwvW>2jBqTa-_nCO^Zmnu4T{o=B=oL{C1hrV2bmqIQ z?Z#sOR98sUTsI4!MN_Lu(!Gu@^0lUW317C@719AnncLPZLMEokLYn{2BGi;t*3$3i zwA-5iO3;H^`cezuZ9Mj&?2@Zv3VZJ&L0}V&|#aa`|Ut~%6g{FEQGuR(W?QVzLTw$!} z<3Bhp;QT?za-0|8!ng@~u3RPTm>x?HORQ!iEeCOqL#5VZfBVI(%~fPX-bPRXr`CoRV@Q57RSO}@K!kA%;r}&VptID^9YG<-9}wJ0 z3Cfm=AcXMYcGgz}k978xv=d@|(+!I2od@Um++YY*OU9(YWIcLoH-0Gx?`oI-+~(Mp zyfUBrxSB~?OiavApBz=n+h4PpzOJxVs(Vet@t!vrbf5mFQHDK`j%GjpMSn!{orAA} zZ3|jUgsDK~sDX_B@3SgUeZmudT(DOl#02rD!LGk-VX%ul67lXKx4QB{_?$_U4-ttUji4}AF_ZB2@4Az$jxUbqu6w);^L{4V2{)l&an?Z z>O&j+-rK+XxiUGKxNJ2`-ZIbaVn-!k|K{poK|Fb-(hRZq&dgg!-q|;h z{@DL!c=Lc_Bv-ab9qjBpBroqOMAzGoQZS(*qPG?Cl0qbMLj#!u$TvKaH;f-Hm_h|@ z)6!jCEdlsOh}^j)ZVd+O{uDch+iQ!su6a$&S9qYX1P!z!`-0sGJnw^5erp_*SWQkj zd>Rm?r>ZC@j}BnU3C>!qFB2*}-FfvgHweep;yZrH?sd5{w?*zLoU3+00qm%Fx{WrO z$>p`6m1GOC-HmtiAevl85LJ{$G?UM{?{LW`0fT-~ME%!ep3En&O*5uCjcX^pFvY{o z(NS|QyP`#3(#{MWHI;51 z?*ECzgs#u4Tc&q&rK;;KliQ3)0d`bEr5!aJ4$tt71@q~>xl#oE1uVc>bb|hD*!iCs ziwvHx7!Z|ve9D$ad^hKtmK6+Tn0@1!%(Sz406x<`0hj0M>gwTEArAMr1!*|!!|qoq zzm>T?qF$%-lBr$d=5(|h|M7B<`hvBROt5V@jopAE2z>*fJRW}UBXFNz$_H9)cnPWp z`OGrb5dP3LS<)Te6S6{A^D<#$W{ssb87Vn{aQBx>YE-kcP_3lcgy>XZF$crpSUnl6 zmuQlpWgj*ihtIS+qoUm+Cjd*l8KghH4koshCg7;P{{-~Qgau1Rj=S$cMVv_La})y7 zRDATwnLUpk&m1rlL)+9Zo${e>=V@-FD}V}Qe=Kv7W9s|#9Kx?7*^GOYOaKD~8>BuL zWxxR{mi0xrp|Ju)axek;+|N)%!jSK(nvVkCAI!dd=tt-Y#-#V@KR#)BtUOpy28Gu< zym}&;89BD@7!pt6%$FREv7x|HiN z!z4DS-09FPF0}Chj4kn8NcvEAws3gTGSx#BPny?2w9?N6F&t zUNCa$lpo^3#{#fIhc*RO1^M?GZLBGqdQfUh#TbBxnXn5#R$NQ6p-4M%j3qXtrm|Xp z-3dPRpRRj=|72khyRTp?r}17BT|xErQChXiyb7W8femxQmBd?%P3Y7X2$<0>ujOp@ zSPWO>iyBn5<}&9o_j(&>G@4z}e+;XIKo;g*#O%{@;gHCZo^ynXn1Q~aohF49&pYWQ zUtOj29%1dkL$%zFyL^Kw?CMGj7=sixH`;C%JuP_aC*AFk-9&u&+xf(RQxVhjt-qA; zTw2FPOs!pSFKV!YVnE_!Sz{O(1b)kBh4lLpm%JTa_x}C@-n8H=zMy-GRHy&}5vdw+ zT|dt}n`|XdbKHIx-uNh`4Iur~QO{c5mjA}(Lh^}xf(|VG>6!RL1<)xi3)sBjD?H}s#bKA$!C$v}@qeDVc-Hvd zzcWZyzc*^NL~yKVPi3nE(WDi2HhTI zU`q6186?+SO-u+anz5H@1o5(PHQXSf+7};z0<9?|%P>_e_Et26xb*Iv7Vh{D(3WL( zmGdPKO7sn{-DTMMWwz1gFAlyg+04t5x?<=j}36x%y&KP>x!X>E53f((?n?H z0NJJdIUJ1S$f-)hfYBe1vm#RQz5yj8-2u@6$hkmh$ca?$fchtVwWSCV+a2ALSMcM@ z3v0Ko_B#K4I^XiAH3Cc2d@VHK#s(E#NJHP%?(bH^^@XK*^4#JHGcJ-kh6Qu5R~ibf z^U_wc#fl`WU^WUuy+j6^0U691-t+)ha7zTB~`v%OSD`o77w%X%6)Q_80 zmfI$8vPI6n@5>8$IOa*Gh*w)&b+gw~JgZ{R*8XssM@5v?&Jm)5CBav(`N6Dl-4@1Q z=Y+ueU+QnC&tUDUt?*wF45{Z*S>w0Jz5#yJDiv3=zbpck<^wYK*vQi$z|Iq^Q zpOwncm`Axl(v%+&m{W|e(OW2xEk&y~y^h7G13>*dr+d)x@7OL~b!uY!EQ#YT^Ws&8i_FuNH>nM9Sa=!X=}thZz$ zlhI|G78`Jm3ZJY6)R1>SgW6wKbC21CQ_(ZkOn?EgiR0+zcv4%O@6!&uM5M zN~0xc_1s^~Um8*@h(p_%f<3&Z#C2P(^=B0H*S2H;{)1&PE#=CH(Z^f@$Z<{|6D#=< zemwX7k`m6pke45Q3^gk^Hv6&2~Hm?*P7ooW{%C zs=rRhMy#@0Pp=IR}>9WIz+0xaIkrWBRf`|GSRldwD7n-AZDquxKm)kUr3o@YR zC2jela{(>zd{0eY=TMKpdBk*(W@OXlN#>aKj3sVKrazrJEZIa#4aWF>ejm6Jty+ zq8vAbx0WdZXe>bBJ@qG`aDKR_AlvXnadrgqD%z$Fi( z{yUy__e3YP8dN6qg^I8MA<$v;V^|Y@?&)py;a1qn!<(M6#d^Hil2xjxh_4x=dqMPK zDAq%eoh>t)XO=`enVWrg)b}NOHwApp;rmD&_5`rV$jG{BFKiDN%kzKWpU-t~w83gJ zo4nTkNBZ+824k^;CEqmgDy3+I|5ubzq=rwTTc$Ml}Ud>L=Fu%Ji+D z6<$zyTpV@0)LQq|@J1Cf+;eF6dlPy953e?ZnyacAH}L&0@C>giYZQ9=jY67C98F*d z|M@6-0;hXHs1iM`XG#jd73Nezoftg{Zw3;npvefFQBR5hNpH9#roGe6o}sz#7gzUZ zxI&yaUe(guyc3C|g*wOtPsoNPzK&s#P-0+=VBJX_kfy7{~0_+yDdkrq_H>1;JRZdZ|i+x<8q+B_P+yiza-+iv{M&s8zG_C&DOE zTF|94GPTgJhSO>Hf#*+wqxu27b*@{5N_YgfM_eC*-Ly!f6Bc^6a7&fjCw}ji0KVc| z)^mazS@K`LV%WxSZj&tzDGibRVG4eQy9CFhciS&BXj`S-u_#SDTHTtK@Th zXF{M2LP_sTS3@KI=seT7h7d8fwGP0PmLc1$r+^-2T8S;OO`$4N13)a$&;k)6LS1TD zbZYBV0$vw9wFQzGf1J4yOkgh>?A2S?gV%v5(i!f1bLNb(sn)i~fwi{(vqP7VpDvsLUYx(tnrgoZR36LC# zdP_A5ijgW2aJZzjsA_*;$@0tWi2g!sO=B1Th6s#;^gqR!EmhYFHH@ z?5-F_e*E>(Nx8OfV)o}M2!pX`Qz+e&Z_wI|ViFtdU& zi?tVj!xS;}(VAJP4bprnvqE4UO;aG4%#P>&4eZt!Z55LGL*_Zqd_&Q`pzoj)jPb>E zCLi|ec^g{fo)8Eq#roCK;$&y1oRvig}j|d-cpPxvQUgUSUM4J6FSr%VH?Hd|vs6sk78x(xm^-5(WR#a6DMIu6+O zLE|wF1Hs@J{bRH1#u7{F@mV{#9LAv`pb_9AgXIgq9IRTT!rSM3pW_61nl5CIiNSG8GCV-jeZVcl&V3dRx#*)^awUBDCq!TWuXJmJhOV z)~F&33SWL&@_gD5Gs}pIfdPvw9EVqq1t_?U9)ZbCDX&f4DQJWWy9Ybz72!Jpv#BhHOLM$$^aMLw~2+UEm(I2$CW7agmv7v1~rotrP zOh*>BPcE2nRUFMZ{T>m2aLWEKLv5Z)027p}J9(pS35PxKw8#{#Lx9AVgNhxFR>Jj) zz>JoX+_xs+JrQhu+IX5Ru?%2LkWFtdPA6qyc-Er~q(UWAk%K4QBH!s}{hn|Ql0n|F zl@p%Zt};_M%;V387FStsT7Z+{)FZsKbqLF@jofB~6~<6Cc)6*4aN!-Bt&`%ZLP?Hh z2rurLKr{#u+1g)U<=F$_HaxrzIkZd{8ajpAGSRNi8G7l@hP{4{o z^6?Sv;qMa#@oW7W4919@%)aTNR{?huH%>rGO=gSvsx!7jzq8)GqM#X8Tv-v_5?jo7 zweDI6fJD(4`%C4BP&;gkX-2Y3(P)mZ`lHDZ&_u{yEff6ya`!-?e8MhlV~?G%2=&TU z?3N~M@^CCN!UIEB?2V9ee18gTL-rROqjL_W!kDu)o%~okW&q4An;DQoe%1wsqsrbp z&$<4%v}VPl{bbPbCE( zJAS4;u`bTliEyrSOhs~%4kY%zJs4~KO@5jg8%|ga+8(VZytFYq+<*F>JM064%6IcK zv8Pj;aMZnYxGbh^{!w~czdu6ygP2h(F=ok%bLr~{y9U$E>|@yQ*a`~^P=PQ|Xa))p z&d)YzpI7))5^gKp8&Hf=hkYRX9F4w?y*|*oh7oHvXk_Q?vT(I>id8VT@Hn}&;xg8A zHlhsEEcr<%Hnit(IR7Iz&4J|{64vWcgL)G`oAapDJFi_ex8Or;t#^zE1Ydp~)-5Q2PvfB|s4AoTQ&=5Lum| zYay8WdLt-vH7Zf6J`==4Mz%*AaXpiJ;VbXS9%yPa^dT3h06F3RJ*c1DZwfcLw7D&IBP0un@L5 z1~oiT2mB7CT1>y{{+yU67CAtyz!DjT{vVyBkjK*9GtgIHqor~h&@|MkcH$FDH}3sCCO;ST)|cIQ7V(2gSs+)9hHk?mZ@|Mpw|clSB4;`cub z^lGrh{d-^ffB4paWJh|kCIEK2vX(R?1sKR`?Ezir{Wlxq!3ZpXZL_ZW-+Q|M_ybqI16SPH+k%cY^#5%SA@qO+n07iV{^Roh z{l#kT03w0wcdHuapZ*{BuoqasVAxmh|Hs#QsK8{49=}D_8C&lOc5nW5>ds!}c)rOO zw=_Vci9z6K%L;3*`a z>wMA_g*nW>|FZ!Ge>{c3ng;H_|NWo0_JtY>g;X4BdnguZq{a1~!)K`hXZKesm65>YIIIH_hBmpSNXa(2tCV^qh(X!wXXzIJFg7Vw^c3OBu+ zX(NqT##pmg1epTy@1`AY8tmhh-?xo64m8vV-*p)7?$Hfgkt!_u7hfHh410#!1tv_UFE#zF&@uIDQQzH z7?fUbwWc928I<69lfIy)@}Heb$KOutrPJN~ab@!i$dcf8Z<^lu*DY5B-jaBA+a34r z`g$42WBow2TaOr67Pb7>6)Z{{c-9vJ?*3AOVQx>*@6iEPv*yy#j}kMHcw8(ve7U2EgB?~xFF#r*45JOldVBJ%3# zZ4={ElH2S#aC05LlKV`}5voimjvLDZgAIuy0iBrhmUKgghAyEGt>!-qhRKZ~_t=Pd z(29}=4>+D70?Tvlnm%MW{49}?`mG8(X~1M?bzp}fEw!xmuKyX!7oPx+xf)V2OHH|Y zElw_bzVQnb3vDyy%?!`XCj(Eima4$~Yd9~RH_+1vGG&3q z8z_o)%$Pzjm^>Blp_bifsjQ6hnP?q6jbfyPKlQ!BEkIU>ks#@ zsiu+^R}cdE_=3M$Eb!6RCvz;kzEcQ+2>Mg$*)`an#>>kRATG!(8K$JAyZ(Kf|Fr(Z za53SI{rjK)<#||(Xip+bZnP(Zw8oI45yWAYC1D;8G%e#tpX5DlxQG5+c@x!vb9=AZSEtz|BG6<6ohFDu&8}ehOz{aB z9UXdiR2$_{7$a|^Cm0b>ZSe&7>s`_0kw8Bt&r;iK`xg&MY-V17EZsiGO1ji^i)0S{ zj?c0mZ))xkwQ$2r!QGe7di5?MFG1+d*UEjTGoD*f_dxq4YeDXO4eAm$E#_Mm*9GpX z-+(gJHeD2~e0D{7qaQ{vK96hecGl`Z1Q(?~eFEfi*FCS}p3R4kj<9oM+?5rDBxMSh zt&TKksoa5HK1K$4miJ#GvSgXq9L|t6xX`JhO&5IvHw0cLTO;U9bwxu;w@%)3`Fd2X zThdd2%CksWeg}ptM=Rw!RX(?7X z-v4w)hP=}G<_LOtf4j`U8G{>Bqi z7$x8R^MMhomZl*mg*4vNJ01$95)x#YaQn4Q z`1(NB?nWV`fr7vW2~79PJfJEf6jM_}pZHZxZAfz%GcA(DsD>IMs3)~k%9R`6PjHZA zLAj-E`G=|o&Ex>~a#{P33aF;)@l+tpi&BWEaEgO@i#AnKLx#W7?)NLj5<*D#E`!F= zqk+`BL>p4{M`7<$Gg53~u%S~M@K`xhq&@RQeiQfv<)}R2HHOAzZ$5x#r6mp3V z(3Z)$NS=C+2DqCBviJU3*IZdvTDjNn-m5%R)5QOkApJ95Ub6#yd%@f~ofY2Sg@#D} zABK~@VxW*!e;~h_amIatYP`ZF%e?L^XkYC3Bbd=2!`L!oPzOk4ZDC*H!R9|bO?Lbx zFVu+vCu8ysFigL+_%iQBz7CV>01x6?1lS7O;wJQLLKhv1TH&M zN;KRa|Fd$6gvH%YSI!3DK~c6Du)Pk2@&s{E;^aM3^1Q<;nKtOR`2r6)T0w$_jS?B^ za?3M33;jnw9=96w(|jjHGg}O64C#+j5BivPS%-6Om_DWJ#hQuW=`YmNPh@ZJa%%z_ z^x5@~_qI+%&Ze;3KeXH`3NQZTF-m~u7BH2Zb_2839}{W0OQA08jZIY)ZSPW^z`eyp z|1vm8W&Rd6%!D-@LsBiJ+uI`;nW6cd5?n3PM=O%)ae^A`VLrd5UaqHBy++w!L7Y(C z0M!^QpaVLy*;Bc#*UX5_63Ao@6 zV{rKSSn(RG8Yf=nP{btF+I^AU;ro5I>Bd5Gtb!~d({kZ*4EsC>2lVr&|!Y%gU1FN14kBgFD*yBuU%A!dVjr8|vV8|*LO+T+~ zaK_A8GO>iiiDsjuSjP3YOD*~&PdR~BJbPJc}`3wOqyy$4f^sC(F7y6<25V! zONlWUe?{T?#*E26p+RygLlsWw@>m2;GpvaO=Tv98`*c1L#arRhWCzL^e=}A%LCV*Q zks`y&SYdTag;J1u-1Q>frp;u=mQk;Ka|>kQ_4XVf!R*vLaOJz(|Ha;0M@89n{o}U^ zq99VzDJd=8B_dstLyB~FmxzGUA>BDN(hVXpbPnAu4BZU%y}0#GpXXigTEE|)?;ox; zI^%Wr+2^xAd!N1cIoEO8j6$86RKY$w5WF!EdZRd%G;NpK1kU(#62^`AYkt34-_ve3 zw?Wf3lABQ1Qts;vTs%eUUeq$f7oAq+WoT1{I~g#CWBOmcwUw3Hc@aPtgFfnIzY4{| zJ1i=q;)P2?`nX6gszQ$(u@aTf2W?5{Bf zrL9Z8qzq1ESA%L`%N5Pn5Z^XAbVbqlitB|bQXZoh$r-+D`{em@DxB~1%~o42I=pl2 zn^KuU*PAz~q2Hvisi+H7?{M9HY91pqb*!k!{06uKXK|}Gh+L@LTX_+BPOKRpD{w zlV=6*+z=RZGd`S)UZzi`ZN12)zyex}C^gKW0tNh*HK^RAca}9$vBnCZ2wB;tY&pLv z=cZ_c+%LO)xkQF9LCIdYZ>q;4-O*LVw_0%bPJGx9XO*9l!KC@Oj>v@HEG>diRB?)w zokM9({acQT*I!m{)X6-1wBHknM=~b{6H#kj1bwI^6t46nW$(YMd)!T(p(@u*Rd^Oo zMy`3ivf!)gS)3YHm5`;g-t9%#+%{_TNxK!|4P{IDyq9Gy#&67FBBB+ zsmNbghOeT7i-bVk;kIsz&~h+y`Vz!+#neWPI~v%y;?>lIe<#&2s!NXD!}Jol1MQEF zsTCKHRc(UzF|XM0U)jF=jeR3lg|K?6*%M)MYS-87xn;$OckexO7N3M+7NE#ZBN?LRC4`E`f6^a>N%xEIJ%pI=^a0m zHPLY|j4yBYOKk49D-NUxh)NTG0&bJJG9M{p$r2VB6L2`^0T!QI%gyg+`WI+n$GyN8#`vU=`bkmf-;EebG%6e^(K$-X*>?DK5)!N zDv9CFQHmO#9d4Dn`_rK3-qvov0h@e?*wvXr(<|QGeMh%G)~<|*JA3O>WJPlMHi0j( z_Z6BeZcYPiHt_1yM8_RY6&q}?8ruDEKoC<7x_~cVU%Un1OCy@)ZIp#O$;zy7q2nPCL1QX~i^P#^PiVVoOE|MeAmk(EHe`|tUMPCYUNv3)|b zKDuB~;Z5hG8Ac(+C4_LMaxrDq6JLkHG+(g_+>bkVy7I#wEv74e=S0`mjFv^5fAr6%tEH6R4htU?1k-hG*CtGh=BU*5z(T))zf`!&8IA>Kqim z<#Y9s7kX03k}$HnzqcAXDQrY4FxKL7=sI&9*HS14-H+JL2l<5KYKKJ^o7v7&^^n6P zC@pyFS_Q_Vlc{l`2U+VMQ(E-vN$!FMX!otmTyWdPn!FzRwAl|!h0ZN0ug!F&&wqO# zPOkFeX~JY^Mmm>Jq_5=PBdHwmYP) zD>xF7)^lE9&`5R<9#BX>piQ?bkVz2U*h+^yubl-&lw3yLn=MDxd5zv_NQJn0dJ!>+ zzl8=;98_Jqf+{R#LMM=Ph0C7AxNqm%mct?W|ydgPg}k?ZY}coMA#A z^h}7A6&XIxLop?%+TY2T94{!Wn(WU=pHXQ)kl_C8C9==-^A3 zrslAacnsOF}$U61x0SqO_vH@?IEA{Qw>TA*C))Xcy*2zObEn@RXguh3b?wIKPNwG z#G+yucd2K+6}1TqlpF#1DD1=2)KqApTpnk1;>+ue8t5PVyY1!u5F1bp4Eh*t@l3D#*I7ZO~Hn z&V#z^C;g<4eP;_YidtMUJ?PXNHN`UmCmWd*DpVA9VPa3(Lc~Rqz&?d9D+v=x{Y>`< zHI(0WhEqg{z4KoRLG3g>j7;RTIy0;8ewlv^e=}wm-YW2hdTjwsMdJ)aUc}e%)XIW^VU>~e-yp+a$xCSV zCPvY~5XY?16aOV^%^kzN5DC2NQT&QQBg@j<^S#SGxor~^nc@fFNJ{w!BhE3HSJ7?e z0tKYybg7#PxI9n9aMRS}{902uo}#aiGLo^5AR(ts?9aM+DdcKNJ?Wke7%k7Z;9ZqZ z-<~i*vA4g_^=;M6^m7Fm7dRxaQGXr#sOV_OmF;vJcE>es(-=qZ$?-0aC)3nXKu6=@ z*MJZP(<6O{9rC<4gYu!NhIZau-@)&v)FmA4#ki06xh5Bam~gnl2NczL;jlxSeMHTJOD?hm(awBnV^sgimwY z^@)?m6$#Dzm>ah~CDtgU)nLI^KATX(Lb*C`zW8ENQz`sqeTG~i>wWt-{D&M)H~u&- zavwy#&l-nUyGZHHKC`wBYGAiQcqPRtId=^?GD5mGN7Q{j=gk=$J+v-t@P;w0Gd=dm~t8*p*kdH}rY6 zR78Ud>pw$w>dA9sks7W9Z+w)|P^Nh!<}xG}f6Z(44v9ATJ7TaM@|MM5^G%1j=v3a!c= z6{y5MU^w@0lx47PA`olly(@5Ty6kvm84MC#CeNqNiDs% zg-QG98U>-hK&GC-mRqIYK)D*K#P|EShdY?pjZwPWO{&6eI8aHpI<7CL*EfX$5}Gv_W%;lMmW3(8Ai&^~Z$_Xxqv)vQ% z^v&4#MOf1$Bhs0(77XPZM|^PFdOxl<*swSL$!w&UMy+%vJ0+#55Qg@4Bc^FMcY$|f z6emu&ffw9z9#Je^IB$h~s`K?{nbgcTQU(g@B*=QwXB0b%?Ydk&P6ccO8G4Ts5_Pwt zMr)Z=!n&m`s*=G^KD^!q&Cu16Cj|yD{RXyRECPAgJ91z$w$1wZn(Wiw)wbb9*_<$X z#htw-K0pXc?NMPzgzko`*lX9}g06DEy!KP7zDoU;HV}+x!fI{&jnyh&vXj7d+B3z;~xbEB{98=GDxnpo+!&*xci>l~zT~i);_*o?=wt zV6M0-2KMxAV6dl}sv5s1_)q`;G3d5s2zM8G{L?=phQHXeWFG(+=V^YA~ z>`@Ufkk+>k%E}ibu(KR>*13a0>cWV5KeLA)88{6MwSyjr%Q}Fj@p`uws243=tC9m;B zDdda#&8rmXB4!fbo7#CeJtmdhZ{;_1nV&Y5gKo1Bey2j!HAz{pj{%M0&-FaEmBINC zGV)&Xri{)B9J!6zGgItVVwE$_@^KaJ>i{w6`4|m;T3@FYc^(B!<8zt_I7y+v^;N_e zHqaWW(ByXCM1UIHk+{@}tg{d;QDb-ckZAe}eb#QS+7?4{16tY#v;w|-ZSA=fFxC?( zGS(pZ)RU7`;)AC)DbN|8RiJdmu~`b-C&o)M>}Ztw^mr~#NL3zVyt-h zvkU@o;0KBy`E_Mp`lESg2&Fdeaqk(3S%a4GD9*6-CBVl&VC3Z&$Mw%$BU%6L+5MzO z4lS1dn%}Uz%fgsM2g-Qnu)P=3gZ?Tw`Qi0ABThz=c3~ij@*?KL57d@Sh_p(YTHjGk zU2v?ly&i?Qulu7Bg0vEflDtr=7)sSG?bNtcR?B7f`rZ*`Bm`6ke4A;)O;VX$YXB`m zKQm^xj59~ncln;(k!l(6?PbEUry6Dt|;SW$g%-Z01+kMiH_NEqr}k zVmwbiP>;&mT78dE72HqyP+Tq082VObyWmA?HkS3b{#BoxVx`@_y^?;BuZa36yq_lv z1q`2>+Vyy<`6*dKGWH7U#Lf)!2h~3LdwlXl(U$Mt<9z^zdbxWzKkMDnYo1rV;U#3> zGO~@>0HUV2aP`BUok=q1MH98)NNmepL;+vY0A2Ar(UlXj#M8jy5;*kJ&ahKUyrKV0 zF0zuWIZ#oiufcshw;rDkwAS~SoAD8EGbh?$ITe24hbeVQsqyx3b5#t1YFugIlf&yQ zrzLjWDWEA!RqkjOV^+NKy135GhZ=;it*=vYn=!0FQf6*pp-8Z3z5m}?t>W8zvb5?c zv`GKksskI3w;J34LoiivkBSTe$GL6~`OiIxPg~&I1J3v>nX1?VTmFn7m53*`deaqq z)WOZ&88i`-!rm>)%EjkbCv+Ctn4JYK zg)^qMzy7@GB-1Co6E=^&9crTm3;NF4KW2vk;wFzZJeLN)^Pq|pox|)ZoEaycI6z-R zt$9|C0Ic=+2c?a2O0mFl_Ob0NEV9}0yo`AJfYvv!r|k*Qu0r7q_qE!L(@S9k zFRmW*q{~SMIc72n1$^z!yN17g6c)h5oL;M+$kj>u_>$ypoLps<=tJd2<*24-m>0w3 zn0&QdPpZ;2dDs@ayAUa_>?qMG864+yhZF|35EDC{k9>x1$F5~GNkBCN32GfHsqp?Ba9cOEC;dTxD-*c|AGQhcJuQ(Fk;OgLjhzP~!_26bzE^$7Q@szJu3lb3qZ6#D> z<|ILt4U?Y5Eknc|1L?O#IYh>at~US@7*xpP&c!RT^GQfk-wpD`xC#Sr`^YzB1y+Iw zCHuouX_O#_uF>^D|Domgn^p?e`#t|(BAd1f8@lbA$$G4_n*2KNugymC2@bmHh~28s z_#Ql~d58RryrwyKPh3k~2OR+c8-%TZBZhv*N9_m^+YXj|fSy|PRM?2!*ZaQum?e$7u zRQmF?o+MZ$Z#C7e{Ft5F@sdgta^qth_f<2|(>=#UPpX2PCinfPMS)y?g-8$b@4qaUxDznOk6=y>VB zp%ivwZhN^c6j|Xy&Ns`Y%yt@<5 zf=@zF>Fu31p{Z7hI(M0QYMEN{LZi{}BN@jp#7A&F-J3cDqU26^1xsD8I`k3dOabZ zKMF4rIMQ9N*=l;iKttB;RqRUGUMhDL2i~yjd0i!{94ak7=6t;;q!-go=z6WkcAm7p zKm2uM-A=Hs6(P#&9y@`H!Qq-iT((Re>6WlIJAxX$h@fM4V8DB(0Vuxn`0Ke z8!YwoYW)6wyb^V`bV)N5-Pq%xw0FbcVQW{O@MPzwXdYub$$s6q&4DiwMbbn%HY@h~ zTlb2}RgT*aqx_FJxsNX?q?hY;zt5IAcUfqtU7E*A%tUPm=z7wUoNHd#`EB@GUybm* z8?wEhK&k8~2W&ZeU&mMoabLH)T&8OK%6Hkz)w*d|lr9e*IdbVkf!+9JHuyOwGKu!Y^tur$Uv`dd9xe?$y1q_LeL^afb9^|(^XMdfKt42#n2p6#d)=OxezWFC4{|Cz z9Ds8DMTxq?VI9?eDHgYBW`FGH+G&@F7^2Neh4o^~(uVFKh1_HnlTp(b_PL}Xj)t@A z0SmlT!8{I&aaUJJ=)7gE;Js)LLt9t6y;uur9FBIixf_Z8!L``_S@OASpNI_@oRfRh zaVB!0BI#l|2kT)gug@logRlDr0I$Tb_LQfY*PVxNVe!lx zr}Eh!x~Q4e4pkKa5Dlef7OLgHx*9(5oKc<6-B+=N4}cBfk@Vcr9&6wot;DA6`dxp~ z=28%K@KoZ0vM%@0sBv9i8~vZ~^=Y9a(POIXb#y5}b@q;g1&NeRa`t)?Q-0V<+p3#Ose@hX_8o zUTK3CG8O4%8n`wM4@Ux*uP15b>_!wSbWYs|M>;ub=~zm7+MChlfZr}A7WK$gR$jbg zmJ1CdnI$;--ax4&@2liULBBKu4F-wAG<-R#ueSQm`m#r+Ng}+6n{8#2j)P1U6-Rle zq0CLEZkMaLCEx+7J1HpEvrqj)tdbY0$L+jD17abF)t0y_Du#LZE*eW`r$R&_J({f+ zF!*Ja{|O~`<>Ff9evxivKTb?s%3f1Ieey5SU5IuS>_($Tla1@Ttbw@BN?^#+irQJ*ez6&;aNWnmrW;R;Rwd-yRVVkkM z73!&g-FU{?;-`VC!!tv5{N*vH=6*HnYz4NOrh06(L`=4+>K}?l+x6i1phh0ws+4;G0chVpo^g zT}APhhsplnIkm)Wp@FUzM^w3EgBd~FOX~fW8!o0<{}p@ZYuuDiR49aOmQqff2^z5p zdDdo+BxMgyHkDBKlbQLWABu-0Y7wtzHLXiUp{bf(95S!9l{Cc?o59YABb%Nb%Ui!! z(%fMsZ*m%qv8?WBw$es#zS%ctSuct|$GjLwZnCwgvouF%t$nU$v#CU5fq6kZJTkwO{fND!V~%2HdbgOte1J6swcRz@9@?)SI!)yyu;n&dkAO z-NO@(^AV*zIz{Um8B|Psi>oe2q}$~57sUo1M&6N#6vOS z>Ug|MLc*67<7$qg>Zk*!uf6%2$rF@D zc{DhW(;n88Kx_gUU#GGBj_1~S9K4c1xpJHdwcs^0EV}b$rB3{&@)_|`H%F$Lo&36G z6f~HVMz>N-IXi|)rmCYOdVgXzNa@bUF@@>RMfv&#vjIjgsCr(raa=9b#4fc%G>j)U z)#uf`8yqdJs>W7ZBZVi~&=^8#cC|4xX;j;)+@Xds($X)Kx*|=Jr)#=6TbDkQTH{BL z7G1@sCb(`b)^F;M_4Mc;#Bh}M@om19$uBn0Z1}>{6q{ga)?u%GPn>uf>Uv{&5gL-& z%ti&<%Q%-Ubr|ZNcq0X~vpT&t)w8G;w}dueH^GRKq>LnZk7+d9<4`qx4J3s~uhy4b z_J^nxxysm`X3Pz?To*p}K9X4{-PhtjeN#0)UM$&XcC7l_)+#u*z4;~_StIMkbMHny zBkB*8qgE8lEA$$%Aakj{hLffQS- z&CDOwQnU!7z+-I{vr4JeXpaZfol-s((xm#d4$P5{R8daYj~JGKTY2=k3S~Ui9tB+~ z-N`9~PSZUMrk<|54|Zd<>gqLuczGA}mStEsl#%Z=+l%mDyxF`;LcVKNs;|A}JQ3Hs zzOzOOHZrc?9Cf&ux;{VZO6<5mBN6!@ht{=QNw;1 zzbR`&mv>gKJI_9M(RE@>35l#y9epGjFcf((Z=Dny&q$|cV^%L94pC#WyxJ{Zfg8Af zL%J?|PVjm$Pqw-wq{x|=KpxygQ#G6Ec-{xtaU>l6O;1~B1bY>G|LU+ApMzs`Xaf3# z#`CH{y^fvlT|&j^t3S>64cTo#_%nT7E$;OC1 z`&xJku;vfoiVB7%yWaJ;iKg3Z9t`q~J+U7uZX@>`$>Ix$&~8wwj<`QNt)wBIV#7`;JdGN8{Za8cmz20RMqh{T`#>KUt(@H zLk+1U-UYN%OA~)*<2y=@fsuh}0eBgvf1~vzHu+2|)^Ip7{*&CSgCG|6ZxfkyOBFnS0@IIcl~y=0jl9K~n(hD#x}=5-a=lMMfWM zOU_QQ{9UZrls%7LFDiG}`YM>Xd~WDKYNMpZv8qe{whLsO(!stDzof0Px_NTn#>jiQ zXmhCt*P4U^QrW!DMl(Mp`ON~C_*kpq`o+mb`Jv-%wDYXB7LgbHRs3^F2rQ+++16~V zXnmQF%Q_Y0RcH@P%4_Bo$Nuq6JVC3t<9vTsNf)NOWeFt8E5>Z)vX$Cu z@yL867gu>Kmg8gi6H}qpjnw7Io;MS?%R$E0kszzm>XtmpJ^21ISEwf%e$>N~%F)z@ zx$9=@93!$stoiF^%i#cE2NQ`!92+8|JF%Er%chp~EsnX3hpTFAXc`FjcnW)r-IhbB z+vC9`u^Xd)LN&Z8UTfzvlaK!v0FnU#Al@qx!oU2~Pb|CAJ$D_xZ~GlAgqdhvC7FY? zuI(b$u(#CXiIXHnS6o;a7)G>r?J}NHpJQBX;;vryWU`RU2vXv0ugQPXHewmzemNDj z0EKFcchIU;^;<L`$`(Cv2LS3~XNbBu;riQ}in-mYq=$gf|U0<7>Z zJe@3zCYx52S$#(nOE}wo^YE3dnhr*f7?hKDE$K^o4&4coq++BmzTPB0A?3Ev>R>Zn z&Lj0=EzeTbbXPr1_<;79-_DLVi_P?Kvv*6MKBhX$$WeoYnZJpBd(`$*k=8L4eE^lT z&`zR8g87mEDo8KLz$j;0Nt#B`R{lcfZS5u=XAY~N(@_@Gf?ZVq+vjeXGscGl{72*P z8lFlmb>~Ik5v#m0AAC;E_G)f-evgQC2&`pvF_^(dJaxPB_QPy~+8*ou1*m{kQ%mEK zxU~6~9?rG0mU~e_qP}(?fny-3xcwhvQ}g8?ug*kLbql)A+|P?o)G#iVVuGiZ>qsIh z8W|Ut5UWJg8D+Xat|?gA$f-o4N<3>EJ8FTPftk5XnZxw-92Z?=d_v@xp@IR%pnz*u zA*1FxhPjKpPo+TaBI}y6!MSssE3nU*D8VqE;o@ww9Cg1S$hfUQe!Xg|%NS_#$p@NF z6LVf9a^OQ&YP%xS#*_I3BJl&{&h%h~5->o`o7y8aRO6JItjl9Q;*|3N6y-5|GJ>u4 zM(iDZYK<7Db1{4daWkpC52|i(soO6EIe5#D^zAM~)YRJK`#q=x*k=5yw=2c~X^IV` zUx>LAJ?8{oqrG_;=Vo+}ya#Oz8lE2|%o0R0M+Q_y*~7Y1HSiH?T+A!b(N*^4biVYN z6@1aWG^>f0Nxm;7)}j&6=;={(Yn?|<*v;-h`{QZ(Amdbb+Q49Aee>xoE(wh7dH@`p%$iKFQD zy4D}plplb>nwVwrH6YnS4Iav4@vHVya%_T)zN+GZ-LCS;ufuz#DjR#y_dFq`9{W?8 z)aj)U6QM6z?fVO3V{&4RV3LHqx=J7=96~Ej_5{_6wgxVOF*A<>e`-f)N13vBWGRg4 z``Gy;?6n-+^Ss$fj`=taY7Zqr%YhGG#nD(2)$2JvlJ7J5e010c4c4Mp%NY(8n29_c z{|I$=*w4txO-;P8Phr^R|7Zq-RmBaeh)nmbYNh6HfsHNB_SnL z^T0Fii052TOJ|;_q`85;am7eJB|s}B|IE{>|g$GV#A)Taj=wg=No+b}%^@ae|?T+2uA z-Yr0XOLM3ElLG(#;eO)RGw2pi7lRm^Qu-GyO$fdNPM>uk)~PG^xS^OmL+R8SF~xFe z{krq?dgeeXfwlY1-qAU%nU7*U(rtEbt1S&j!id7-c%yx2*213Hn6Q2~)Ng;eq?(C> zCzmhV_AgKcDhS);KS0us{Uvm`{mme?0#_M%CDF|1i4D)zw?kVwHAnQ!ZNclY70GhV zs>-n)4`VH(b;_Re?3HNZX2tNk3&IlK(lvPA*zWJ^b4v7mP{#0XxbB0NnUy$zQtTj& zkTw=v%SfFVTSGJLp%Fp&%LX2OR~8m*16{63H+7E_Sd>0M*{{liH^Z9uUoe2;1foVK zBspw1<*U0H%+I6Bbwj*o4_=A5eSh+&!hAe%(UZDQF`!<*r^o(oEw{fZ^c61qJiQ-l zK|H+;iY?Brt)(J4zgUXfUpjye`}gH#Q_$;IU%fJVL3WIi0A!k6sExoebM#}k`#uP* z&>XpxwDd3no37PIu+q~6uGc-^QH$t<0AeDTZfYE5t6k_PTL!nvr=aJ9~S1SO0ygudzIErUmSdcAbu+L;w8 z9%1VB=88-jgBm0XeF_z~(dtu^7PqU1MP{Dk{<*a!W|Y#?yiv=Wv+ahnb3|rwd;OkG zCi~Ez8OB?lx$^`)i9pyRjQZdDyv6X>H<)pCuuhj#@s75$5PJQ~mF{OHM406|S{Q6P zCLe)RSV+B9(x{d9*s^Tsg6H06a9T`WI-n z0nXjVIr$gk{_Qb;8xQ>%Ca{er$2Xa!{7(w{MT#_Sz|dyDk^GmZ{e$E0+!Z4y<5r=( zSCsho5&!j@zkOAK@<6UqbK)=e@jr=_CV_we4m3z}jQT~$KQZ{X4?a%7Je^S7|2Ev8 zEcWY%C!gLeC`UZ1u)_TB(*Es#4Kk5&k6P)4{qH0p$3Rb_i}V?j{cFkoC#j|YJDOtL z!uVfFqCg2Oz8LFZo#fx<{@)i%xE`2iy`RAUPLjZvz~W2X*Wmh(CjHA|nE-rdR{cf% zep39)BN}g^S<#AY7Ao>qj;KEf0bsVC&49McajE5Fu+-A( zMfcK1b9CE5JC)e*MdfbJ0(5G+_E%rkYu4J0o z^|x#m>z1WBHf!4^c6Z@v9$Pc%En5@)t?N%6cPs+}uzslSoYrPH1lxAN5t{`Dx|6^2 zO2Hj(;DNFnyz!g7XpdLYJJqGr|X$yq*DpwWX zy+|$NKY@EFjbnxW;ZEFW_fAtkLo9KXPY;ThYP4+fxjR72zl?N`E}Ws3%hi`9FE!%c z)MK=tb=CW+UxVXO-gflXM)XP;2E~0@%|C-W$3n79?;6gb{h$fq`G*ZB%AiX)Y;?cF zUKCzDd^cCpCjYnlinzbT489;TP^5KKmp(uSDf<}g|p)@<#OEu@( zrOqt+hNXm$?T0$PzQM+0HBkAS^J2s>n@TYPp2xuaDgR(tx%T2lN8TtoK~P_Ah0!Wb3Uo}QdTLnZ}uDCWe z7gzpF$HmpDSP9XaA>MQ|7Z&?K7W+~HM)vy%cWXy`hd4#p+5PS3^jN!+q+&)0dfwYh+n++=7OL?3mf|tJOw7(4d0zH>l z17Fj+RIfk#9EZs=5HXLG@TX$PFz|RP_+Mpt@mhBcd&&#lxG{k0G7j_JGwgbIA56dw zy~OD{TSjSr_x8^v5U;PJ4r;dFZ;DLS9gj1#W0J01%wZWTrFzGAcigM9liMQ$d%uQG&+sAd&qh;bDz&g4_>N zDn#=sZKcbo%4dKncJ(UB^Rs29#cp4m@t~Go&|DUO=Ga62@x-rg7&WIIfoFZDDzH_M z@A1?3O1_Q;6+f`{hg85?g&W`HlbL?DX;yv%sS@i+czcc6)4wvQV8--gZJzrmR<5!% zP`ht5wM!E$4S{utzi=L}`xd}7mbNbXI7d@EHh=CgRGDqoiYrS!Z1?+lskkfQrvc^v zp52|h0_YPS-Dh#nom|GEUnqfyeu^uMhG^#p{sW$9=AEfMPJ&-}9yh%KU9+o8{~!UM z_gFQHpA%fZ$83RwP>Q1pHo_O6W;RLWzxY1&@~;y^jx3JlpFp@8Da=8lKUeH~ws5iG zMT)1bYHxgXO)LlMP7mV%MP%T^jVQ8^sA zGTsg!et@S_$Y(r-E(m~qlsD>bCJu_+F-DfzFErHt?8|;uDT}Lb1V}rz_Kj(Z2`(`oOcR7DW8E}(v~h9Xzgn9TPJe~U2=x>n__MHjs7 zXjm1q%sBk1yLl8D5^Z9`Y*AKh+~Go{_e$}4l|MJtkJ4_FElcj~Kh6EGxLVi?d8_Hr zNKr>M!Su4BJw&m&;kl7_r$hJ8)b#U(yRG>>ZymJ7a)@R7=$GdLbb3D@x{BM-rHac> z|1O*G;%)rhufF;4m#2SqL&xK*W6H7OWI4==iBZ|_)+(1Z6yEx&El;{H`*EAkXiZZc zHz#q8+ml>_E{^c;DaJ2Ag>&71hVz3UVCzu+k@c5{|K>CLz@qZ}K8umETfO&Rzsj2yHKYo^`j!4%N&~~4qfNfUZsJ>y{XPs@;V-_Q+dpu&q z>i6F$r~{iRKaLc)zp@&C=zK8jZtG^g{zVauamrwWol0+%Fmtj&(z`0yR4leH zFLud-V%gg=!fG%+P*vmnsDrcO?N4_^*flX^{?3CLAHMnPnNC@k#iH zF=B_^p=y6NlN~h*gG}32hB)E_)z~>9S78mGY1fwlkO+n*aQ*}d766h4mQjiy>i=UM z0nYUC!n_5^OCVsC*&bPE0Qz#Kdg8JE)VSxf=LVhm69!v@G9iqN1xqPNDFdGmZ2{vU z6_-u?X*>@BG{fv44?zKz+?U_aGN&Qe;p*4_8 zIcD5({mEG@x75-43`X(m>{EfSjvr=9{S>Ye5KaSyz50)39RuoRmG{ZOztAJXg9<>U zm69%0KZF~r2Gjw^IpzOpJ$SdtAHC1V5kK+um+woH2fl)TP^9`H+*b`if+EBO%|9?@ zek;Pg@R7ey@8|w#3izK>lnt18M0e|V$~=iKvPDNiYPU9FOV>!GghZ$#$iV1Fz``gC zrR7HgBVRG>EnQ_Uh4x5|$WK~~M^3CyOkQ2S9K|9V)C5#C7g(F}t9&LvJ}1!WyQ2p> zGLd~-x@~Xl;;h~ zBY*VJ7tuF1fcj8FZ3l0s{>Q5QTugpIlij`|!)+Pp--!~Wc$4LU605Sq^38Rwntr9h zM%mdxmvMXJsnArx#r@4Cw4KA0vpLrypb&*R;xucWmatTdfcy&JsyF_x#UC6=Eo?N0 zeKHR->vbK-bFOUrxIc@Orpg)H##L3cZMSR772TF)ic(MHoVJU2+w@8EK>oO~oZ#Vq zPno9W-lD{;#?dJpc?DUPO7?1KbQ}^^TxS(t#aj~8c|fWI%eIQ@K?LNd?9suJKr8g@ zjTc!8aJ_%wA_2==b(u+%*IN~>70;NN2a?Rq%mRr|xXF}A3IUqG4PWU~0SFzq9#tfN zokA&O;*N&8oke4p9_!fK3zE(ID%E~_WPA-~@eeiWoSbiQ+Y9aKfDp$r?hf-W<}pz| zM#n?<(VFMDHynP)<$L(>rmQOJS9^h5JpXfv$>eW`aadrlCqqD{Vvm0G3J_EhoPt|D zZ>!dCo1dTmF;&;z)+>A|F&&CSIe*nN%xS&=`}4j1RojDY z;8u(g=<1#KO4<_lx8ChR-wnAx_u)VWj5MMX@t*?x&Ndo=5Di!l9t8_O^~=G$Z4Qfs znVaP=33;E(0{xFv?PsI1-XF`G?M>ahWp6%z{+zX|TV^nsz@)u=K9xQ?^d*BfA+er` zYvkOhC9j{n8(|DcVVljOjtY~JB91*tl7b(lFsniWnx-7PnwlQSad3WqC8ly>=L+j# zQHy&@YMf+th?#Ores63ZuE2}&wHUXW$Kq`BZ!AWC_}jnP#WM-O{N59(CKIgO^}|V! zs`6DnPD32NXm)+yF352f-cD!DveU;c{L#ARk4@Cx&rRFy&P*GcPs*>QUq=}nBI zykw)z#^~S1gv8O2wT)vS zKtnroHDYT!AHoQN6o^0i#H_>CEWKwoR!*_xuhgB9twaJVk1hNN=jvHLU9Y>cfk{*Ge@p`IQG+iWkgEwAx{YHYG@_TV+Fa#h~uIG>cgX{Uu0C+pl%Xr}% z0yvy_kuLG!+B2079K^+TYocQsi!jT_O8ASt0j+mOh;_5Cwi%T)X_psU1Ni%-x> z!26saeg3>QA8-L;irV$$_S+d;X3H~;8u#Smee;shk`#z{1kLN~h&#=pvho&KI1M=Sq;x^Tdr?1mvl{=g+`b_E9`U>`x=N?7-XZUfh7m9be&4A#I-2s6qu$YCz5 zBg(8nN~Wf;-ba=+QJ~C|n>O5~qt(t^4n0vIS75kX3-pCH zmD9yAYG9WLq!0_jzsE=KhsswYeR9J}s@vGw-%r&o=3yUQ6ZAR^&N!{ybFUDmo-Gk(5}3+W^C+4H96wPt+R}32x?n~GakrMCF7)YKWZetdk-kX za4d+X-T9S9&Vfo)G&4nZ@;Q@2XL_E21u(SRjgEU8TECD$xr`30CiDW0pP^D&=Ymu$ zir4!zi2iL4Bd4s>$aKRw%91%amGs7SVNuzJ?(NLP5YWrkgwMBIBRfrHJ3f2bF+`IR zK}*AjjcvHU%jbr$6=X@5cN z+zzm5ENuxBH;3KIm$MOTZ=6a|bJlU)n}WpUh9IVa&)Wd_Q~(cI@QDH;P=pNVzKcqx zU%t}S!5=4@Bhipzh&0@4a&yyLIZULiMvqUYT@ruIlCjjixp>ZA>dDoO70A%RKUmnK~dJIq1P3FVT| z6RqnM&8YM0diM=Za+!&p&43p709Ht3a#>lKgN<1?b<{|?h^)8aD>KeKW+Uwp)tcp) zRI%62WsIOY2B&FQrX_f|#MooI$99kAaVL@cwO{OVnmlPVZS^)Ko95N(>3DmtBE@h( z&=#&=ew+CV1q z&-fZy`4+EdX9s6%+J|pDuqc)TQoj?t=Dpl3n}BN{W&)H8-X$O&Cy5uBhPLHl+a57* zW}2Ct^eR@T2nk->S~|IG`qHRE=EMz{uiOu7mzQjw^PC}!ZCcJzAcsG|Gek<@Ll<6D+~HJJM}do;0dGr&#d*XpQ3 z8nBH*R9d+Q)$MCvgPYh(dpIhkKZoGLs zkPPI%i#x6r`PhmIo03i4vV0ne)iFe_kHqmb)hn9zW^GNVV`E(_iT2b8Y;R+>?(E4e zFaq#qChMq!&K$G-Hy}g914KA>W!w^J3y8p>4KwlRcKp+d`({@B`W`cau__+kCLns#>CfF%x6tHEi+3_Er&;#DIONA=UBOseK}RU zO_VgHU!A;^K}go=Z*rk}t%23BbqK`StrdorqD(|dmBoX_X{an7gznCF?>eP7RY zeXs3%J+AtA9Y`;ZBOu)jA3Z7(?!Zs=wgv9qSz0Dm^T(?fyxp{Fkiz_1*+2;iZ1+wv+9T$Xj0279gtfd6fF}Te=$?@=HBYpW7rT#)>nIsiEM$) z_zK=R|8E^glu$4|m(O40FD9PA`$sqZzkRQ7_i;Taavou<6YvNYL z7HxaTyIXQ#++X3|I+E}kT29hOnX{WOOuWjsc_p(@F!7peb#}g2cg%=ZMF7xl3Y4=4 zRD@d|_UZn8aw+iARQYC%C2zxGCWz}vXK;P3F#7Pj`doE8)tpPUppIql?`0q`0*@YL z1f6D%C%|e@cq{;6ueRy&E5T1q&CAP0Z-pCojhzBe8@M-)C5GrV9M7F&_DwB1wr=lx z^$%pP1wIgXg{P!8F7RKd{fGjY2JZlT%@iB@LgW zl1*|77Y&~Y;Hu-J zBs1g>0BeY{v%_ zkV-;gcrXDeE9+P|D2wi&QScK#6|DOuK6FQf>*w%WxM9`Zdx{*xreZ~c(=nq6W4*}g z!9I&}U#!^b*Of?NC)v^I`p%*97s^IJZ3xQ<$o9k>p3+O+dMq*Z`J3K%UuOll zsxukyhrR*2O=9mBsr>Tr5s*;JU)S9cV8*c5@|*agkQ1ZuiBdwseMsa8B$5Ov634ol zEO+4KAN7I}3w$|pZPxFze^Ixk+VT*FO9do2=1PFtzxp|E;F`Y(QDQ5<_S{4&7kbdBKycR@wyIbyFs_>jHjsU%aS+c;c}5Np~P*+zSZG} zK{nXd_IfvMHFpb$2>PeGvFEB!-OiQ|=@ylODx@84^42$Gt-pH6go8d_UL>E+oFxV` zShm}hkezRv)-|TnHg(szu=(IzB6|%^!xm;PPELEKBDYKpbDgUe(KNX~m(isSbDd6% zI_v55W$IJ%`>SjpV9`~Zv4$K7Kd&npMulR9>aO6GT0S!@aBzVRTU9n z_p{EMXnytDpy5b<+Neh)hS^NTCxK}`EOF%WjgUkv3tj^+7WTEfO7u$ima}mlAtM@(p7xUFrQiYTUh%PdL>DcLnT;s{s+50) zRPx%)&2l6UHdQYmo|2a?ClM!gI6a*|KTC@VB*t}9I$B_EU;68jx+^xux>zZjrWlSk zl+RHosayyRsqa;II4;pw!hIzM+#86fu!<^Kd^aOD&m((|bH1@;iNI{lp~J111d_yh zS#o73L&GKA?~12Mi8(ZiMkm$+m!sn*XxKkN&&9^(o@PrQ)0H(bUO3~Zqo8ofM9v4{ z)nfm!7+UkTqY)CAXZ)_M%w=M@gqV{BVdPvwKj^f(9cUPinF(^8`V7eFKIY0&C~ z+hrf7T9SHLV-6N5r(B}@+oeACr-sT#9T9Z1>C$`{2E}MscrNe(?BAhz3M3t(#}cB& z4Q5x%zuXDNB8+U zjnlw+MEDLqe)V45p%B{H+PZh?kcR=eNQOTQa=S8KP^QTwmucFMN=2OSnfJFEsCi#1 z>>m|Jo)rueiZp zRXr|-<4*m)9JR<9+1280$r)wHk#cMAN-p20S$M?`yu1-d;-Y3pbb42plz~}b>ehzy zcJu+=Kzm=99-=fYeku%dx2S+9gwq0qZ;f2`9XNrFq; zL1&HY8ma~kACs$HJHXf)mnjnY#?M`|c%FA|b+&$+)u%fjO|w8x{eW$pyQ`p48BbQ( zSNzC~>aab&N1=1Dt7tRVY9C_YsTKBKp}GbB#ZxC{xmkFecQ3Q$B09U6qp55>e?Gyr zZj0^H2J|*NUe@L5W30HHc#Vc`y!Qv}8%7XLfjBO`mOz6sFQ9!vt6G>+-yeb)%5Jy* z5m$9g0xzg@(GbF)KRcOeHPCCW(zNt|cUqzsB`5FVtE1i}jJR92P5wkGq|!dTmBp8< zE|-q*((sgPZ|_(GeX-}K0YZ9Ek3z)h8?DoeL0?JGBLi8NF~YnhWKR>^VQo&(>#* z9=A=KXpww59!j2qKKWviLV}_1@*)qEyDD#I7k63fNu;YHe%7m=R)OWF!_K#c+26su zJsrz|W;XOOZ3ESWP*6?SnLXz2=unhpMZczQSU#S?&hQBZs+P_1~gS2e}b|+h(tEBg1dd$j*n53why>~ zOPjc8cW~e1y>~Os3-7%&o7<38A?zGUY#`pG1;*(ELb5hxz9`Vbwb;d2248z#GLZMq zBlwH77tFDUM2s>xbVj3NFRcowl|WA_XQoiY9tj{~Vb=9=3hS(055)dXgWVfc-(fSb zZ3MgR&jvv0&{>1F9?X@G(n=y}gD;-8L{O$6E}R6yR}pWOp)?wQYB=R&flDO*VajlX zg%iM*cD*JfWp4rF;CD>1>n*A-#x=!AAocyX$tMT<$>Ah_@kr4*N(1}<%^@o}o$-IE zsok;;(@4xesh8;>VyoG?(b}DTQhDz%+VWwpviPYYK*U6F!sT_EaPiv`P|Y}-%i`;@ zxnKcgyhS1j^BcZ{KWK@T*z4*&f^nX(>dmt=i$31FjFND&pMq^Xe7`o`xY==vv*Cq3%Wa_%lhdLEmn` z0l{g%xA~?o7vMl9fs&~{XQ(_}ix%y}qhUcBN5kt{EB=wFQ7FPHhpg#_JGC#b!drdN zIxY_7+K62gZ9E99wXIJ|g=%rdq?F1W46)OXR2Qw>D4<^*;@$8sRE@`S7@2yt+t%rR zqa$qESShSOJ=pj5Gzta9;-ANFrnM}>i=B4!@{akJ#C;=E8AbSWytQ1%S8fry4PK0s zlfB`-dAUj7)VORi!$iidOXJ%{nLr~cJ*P&;ncBb?4K2`(l6OQ?9K^K@3o?!miP+cw zB=CzCx=!BPvyV6HN?%=l03QmBj&`d64Z}fFt-a(oneYh$k-R*pRlcEJ8>+ytYuGj* zH-%(LwAmxBrD}tO!>;&=L_8b`**J0wqoZ7^%;lm6Tl2Vm?n6SH9%G|UH$V&`FtkDX zQ2?5;MBe_0 zjv?#%g|Z`35zQ%SW8Ze24Ux+nBE( z-WG6QxxwlTF>SzW?TAQqP|eH7gBM-W2Wq=ExWj4yjFUc{rk*|G&fX%^EEC~jGjx40 zH`yL)HV+wCr)&fSus#>$Vqp)-XmU7z7!-~?Ml+W7pKCmUm4xlc+rV72Es<@^ck{i_ z#@Wq>)S1~Y8kOHA)NFl!$8OtqOQrn49Zm;8?6ojn>#p?|R8eN`3tqECLW|z)hD$II z2-tBB3P|RvL%o2aN6&?jHZo%RtYwVrDG)rWArSH*Uu-js!aJ%J_)ag>cN%Y~VbYv? zmWrX$S{1$%ULL0EJ&u-o%~2;$;^G!-GOQ?^OqzfMrVtLj-G$Jxs?kps6xABH-{gn7@1EsZOB}a_1af=bNU=k@6&m1cO-365>5Gb}`9vbsnNO&&k+@N#5XcCopxp zsAH3E8JgVsb`BJ5)xT=o zn}7yX{QL>?2gPF@E)+~y+tSosC=n@g#hg=m4bfW43EDnQ4Qxl=e0wJf!Ta)h9oA$Wf@TW;AC7u(zd6Z3Yw;s%K`oVL0`-TZGMsX|VI z3nvvO@_7VwJ&DL|Y*i%_9oyOf@$_*iTqVQKD8kcZLv&=W_c|H*i#k2oRO@WbgLxtP zdr!W5jS_Nw*=Q5bKdjU){q10Y11&87T*Bsb?22qBx03_*6vQQ+UuUl^c;VcB zcCTn}{d^gKj6#;A+(HFT;uf0GHDA}j00(YHBB(QTz=x}Qwe5}oseNn zac$f3%CSLL{Fb(rwIrogx+;{Cykg8o*!10!YtNCn;2YfX7y=*|3L3TV1#G?2ICqMx z;M*B+sSPGtmMRClfXB#WKktsIZb&|pk9UP|ye5a#cq&ZPK86tP_CbmQ0kfHvm$f3Q zpK+l_@M<#!5`ZYgA=OAlF#B|)a!98JcQf~uyF}qL7*z0zUDz{$7@?0*I<-pcKGA@- z?t!neKtpm?x}cgGW^3w$-U?A$?Xt<~(U(LxGeZ1Q1Jt#34w7v1_*p38{E*nX8d(U@ zk#RtP85Js-(6F-9X`~z7Q4GL;Zqi=vS0J#M+!n%N${MTA8S6e$QPh4c|H7%0nd)1f z{}??0Z^74F;F{_RDf?E94)EQ*K6!9m`(Gvx;_@MdbH@#1oEJl+MQwZgiy^NMdP&5dL!`CHS31Ddx!$vax1PsVL3#>pJm@d#To@ zThvO*OBOsD!XCZIwjYr0SLV4-Q4)q5MoB1dSlN^fl4z^Mldtgs!!)|i?AI{WlKPvX z8N<&i3H}#3i%l=|G?WGnxK*M057?Gdr3%|+0<0Oal))JCvrWU=}w<+(QTGr)r>muB7dR+c!^ND8yUgW2=}{BtU-+zhTx+H-5;^b zciZN^KnEg9l@qlXswUEpdw$RvuPGLR^ySaSe|b)SB>sy)zPXn_szMDt41ZMRkE;AH z;W(+8E~e`O0sMVlQ~lgl=v~fge>LpL}!QTJh!EbTl+H46hc8YrvF>!L7hw|N6aa z1G;-D3EqExkWI8Av|&0T5E4~)@AnQ5+^o&;J2y)$)G`Js&2quaWB=x~lrJ($X{sEi zUgUS5O#am;HJ4YA@L$^oPoLDjz<(!%^`$Cp}^f8gSGuJHdLTvXCTJa$EIzkKrm2t#O8RW#3J{cUmmKLC9E>vsSE literal 0 HcmV?d00001 diff --git a/docs/images/providers/m365-auth-selection-form.png b/docs/images/providers/m365-auth-selection-form.png new file mode 100644 index 0000000000000000000000000000000000000000..4757f44d96cc550fac245156668bb00074533ace GIT binary patch literal 291797 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⩔A^vzdUBcJn9{4v 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�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�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%0D8FcC_fQdooD_OIDr^9j#PRO>}i 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 {4i+Z#b#?XurE7?>5#OEQuTB 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

-Cf?B!^vDO0tE2?a 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�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

A}Jy?`FnpHTm56A41XzT);{aTg* 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

- - - -
- -
-
- -
-
- - } - > - + + + }> + ); } -const ProvidersContent = async ({ +const ProvidersActions = () => { + return ( +
+ + + +
+ ); +}; + +const ProvidersTableFallback = () => { + return ( +
+
+ +
+
+ ); +}; + +const ProvidersTable = async ({ searchParams, }: { searchParams: SearchParamsProps; @@ -97,13 +102,6 @@ const ProvidersContent = async ({ return ( <> -
- - - -
- -
{ + const pathname = usePathname(); const { isMutelistModalOpen, openMutelistModal, closeMutelistModal, hasProviders, + shouldAutoOpenMutelist, + resetMutelistModalRequest, } = useUIStore(); + useEffect(() => { + if (!shouldAutoOpenMutelist) { + return; + } + + if (pathname !== "/providers") { + return; + } + + if (hasProviders) { + openMutelistModal(); + } + + resetMutelistModalRequest(); + }, [ + hasProviders, + openMutelistModal, + pathname, + resetMutelistModalRequest, + shouldAutoOpenMutelist, + ]); + const handleOpenModal = () => { if (hasProviders) { openMutelistModal(); diff --git a/ui/components/ui/sidebar/menu.tsx b/ui/components/ui/sidebar/menu.tsx index 2e192093e1..4047db376c 100644 --- a/ui/components/ui/sidebar/menu.tsx +++ b/ui/components/ui/sidebar/menu.tsx @@ -69,11 +69,13 @@ const hideMenuItems = (menuGroups: GroupProps[], labelsToHide: string[]) => { export const Menu = ({ isOpen }: { isOpen: boolean }) => { const pathname = usePathname(); const { permissions } = useAuth(); - const { hasProviders, openMutelistModal } = useUIStore(); + const { hasProviders, openMutelistModal, requestMutelistModalOpen } = + useUIStore(); const menuList = getMenuList({ pathname, hasProviders, openMutelistModal, + requestMutelistModalOpen, }); const labelsToHide = MENU_HIDE_RULES.filter((rule) => diff --git a/ui/lib/menu-list.ts b/ui/lib/menu-list.ts index 2b465869e3..e2e83f0951 100644 --- a/ui/lib/menu-list.ts +++ b/ui/lib/menu-list.ts @@ -18,6 +18,7 @@ import { VolumeX, Warehouse, } from "lucide-react"; +import type { MouseEvent } from "react"; import { ProwlerShort } from "@/components/icons"; import { @@ -39,12 +40,14 @@ interface MenuListOptions { pathname: string; hasProviders?: boolean; openMutelistModal?: () => void; + requestMutelistModalOpen?: () => void; } export const getMenuList = ({ pathname, hasProviders, openMutelistModal, + requestMutelistModalOpen, }: MenuListOptions): GroupProps[] => { return [ { @@ -164,12 +167,28 @@ export const getMenuList = ({ submenus: [ { href: "/providers", label: "Cloud Providers", icon: CloudCog }, { - // Use trailing slash to prevent both menu items from being active at /providers - href: "/providers/", + href: "/providers", label: "Mutelist", icon: VolumeX, disabled: hasProviders === false, - onClick: openMutelistModal, + active: false, + onClick: (event: MouseEvent) => { + if (hasProviders === false) { + event.preventDefault(); + event.stopPropagation(); + return; + } + + requestMutelistModalOpen?.(); + + if (pathname !== "/providers") { + return; + } + + event.preventDefault(); + event.stopPropagation(); + openMutelistModal?.(); + }, }, { href: "/manage-groups", label: "Provider Groups", icon: Group }, { href: "/scans", label: "Scan Jobs", icon: Timer }, diff --git a/ui/store/ui/store.ts b/ui/store/ui/store.ts index edc329654f..48bebf22c7 100644 --- a/ui/store/ui/store.ts +++ b/ui/store/ui/store.ts @@ -5,12 +5,15 @@ interface UIStoreState { isSideMenuOpen: boolean; isMutelistModalOpen: boolean; hasProviders: boolean; + shouldAutoOpenMutelist: boolean; openSideMenu: () => void; closeSideMenu: () => void; openMutelistModal: () => void; closeMutelistModal: () => void; setHasProviders: (value: boolean) => void; + requestMutelistModalOpen: () => void; + resetMutelistModalRequest: () => void; } export const useUIStore = create()( @@ -19,11 +22,18 @@ export const useUIStore = create()( isSideMenuOpen: false, isMutelistModalOpen: false, hasProviders: false, + shouldAutoOpenMutelist: false, openSideMenu: () => set({ isSideMenuOpen: true }), closeSideMenu: () => set({ isSideMenuOpen: false }), - openMutelistModal: () => set({ isMutelistModalOpen: true }), + openMutelistModal: () => + set({ + isMutelistModalOpen: true, + shouldAutoOpenMutelist: false, + }), closeMutelistModal: () => set({ isMutelistModalOpen: false }), setHasProviders: (value: boolean) => set({ hasProviders: value }), + requestMutelistModalOpen: () => set({ shouldAutoOpenMutelist: true }), + resetMutelistModalRequest: () => set({ shouldAutoOpenMutelist: false }), }), { name: "ui-store", diff --git a/ui/types/components.ts b/ui/types/components.ts index 98197ea17b..27de816762 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -1,5 +1,5 @@ import { LucideIcon } from "lucide-react"; -import { SVGProps } from "react"; +import { MouseEvent, SVGProps } from "react"; import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; @@ -21,7 +21,7 @@ export type SubmenuProps = { active?: boolean; icon: IconComponent; disabled?: boolean; - onClick?: () => void; + onClick?: (event: MouseEvent) => void; }; export type MenuProps = { From 0201073fcbc34b3fc6a4a9ca549965eb99145c00 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:19:49 +0200 Subject: [PATCH 05/57] fix(docs): small enhancement in warning (#8950) --- docs/user-guide/providers/microsoft365/authentication.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/providers/microsoft365/authentication.mdx b/docs/user-guide/providers/microsoft365/authentication.mdx index 6b125b4f07..a569008d52 100644 --- a/docs/user-guide/providers/microsoft365/authentication.mdx +++ b/docs/user-guide/providers/microsoft365/authentication.mdx @@ -169,7 +169,7 @@ openssl pkcs12 -export \ ``` -Guard `prowlerm365.key` and `prowlerm365.pfx`. Only upload the `.cer` file to the Azure portal. Rotate or revoke the certificate before it expires or if there is any suspicion of exposure. +Guard `prowlerm365.key` and `prowlerm365.pfx`. Only upload the `.cer` file to the Entra ID portal. Rotate or revoke the certificate before it expires or if there is any suspicion of exposure. From ca55d4ce86b5ed0650f9a4ff390d7da2427b9315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Mon, 20 Oct 2025 12:20:16 +0200 Subject: [PATCH 06/57] chore(aws): enhance metadata for `directoryservice` service (#8859) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 1 + ...ctory_log_forwarding_enabled.metadata.json | 31 ++++++++++------ ...ectory_monitor_notifications.metadata.json | 30 ++++++++++------ ...ce_directory_snapshots_limit.metadata.json | 29 +++++++++------ ..._ldap_certificate_expiration.metadata.json | 31 ++++++++++------ ...ius_server_security_protocol.metadata.json | 35 ++++++++++++------- ...supported_mfa_radius_enabled.metadata.json | 35 ++++++++++++------- 7 files changed, 125 insertions(+), 67 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index c107c68b1b..8e07798608 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS Backup service metadata to new format [(#8826)](https://github.com/prowler-cloud/prowler/pull/8826) - Update AWS CloudFormation service metadata to new format [(#8828)](https://github.com/prowler-cloud/prowler/pull/8828) - Update AWS Lambda service metadata to new format [(#8825)](https://github.com/prowler-cloud/prowler/pull/8825) +- Update AWS Directory Service service metadata to new format [(#8859)](https://github.com/prowler-cloud/prowler/pull/8859) - Update AWS CloudFront service metadata to new format [(#8829)](https://github.com/prowler-cloud/prowler/pull/8829) - Deprecate user authentication for M365 provider [(#8865)](https://github.com/prowler-cloud/prowler/pull/8865) - Update AWS EFS service metadata to new format [(#8889)](https://github.com/prowler-cloud/prowler/pull/8889) diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.metadata.json index 153e8d3600..02f7b89c45 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_log_forwarding_enabled/directoryservice_directory_log_forwarding_enabled.metadata.json @@ -1,26 +1,35 @@ { "Provider": "aws", "CheckID": "directoryservice_directory_log_forwarding_enabled", - "CheckTitle": "Directory Service monitoring with CloudWatch logs.", - "CheckType": [], + "CheckTitle": "Directory Service directory has log forwarding to CloudWatch Logs enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Directory Service monitoring with CloudWatch logs.", - "Risk": "As a best practice, monitor your organization to ensure that changes are logged. This helps you to ensure that any unexpected change can be investigated and unwanted changes can be rolled back.", - "RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/incident-response.html", + "Description": "**AWS Directory Service directories** are configured to forward domain controller security event logs to **CloudWatch Logs** using log subscriptions.\n\nEvaluation identifies directories with or without this forwarding in place.", + "Risk": "Without forwarding, visibility into AD security events is lost, delaying detection of suspicious authentications, policy changes, or privilege grants. Attackers can escalate and persist unnoticed, risking unauthorized access (confidentiality) and identity/policy manipulation (integrity), while hampering forensics and response.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.amazonaws.cn/en_us/directoryservice/latest/admin-guide/ms_ad_enable_log_forwarding.html", + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/incident-response.html", + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_enable_log_forwarding.html", + "https://support.icompaas.com/support/solutions/articles/62000233528--ensure-directory-service-monitoring-with-cloudwatch-logs" + ], "Remediation": { "Code": { "CLI": "", - "NativeIaC": "", - "Other": "", - "Terraform": "" + "NativeIaC": "```yaml\n# CloudFormation: enable Directory Service log forwarding to CloudWatch Logs\nResources:\n LogGroup:\n Type: AWS::Logs::LogGroup\n Properties:\n LogGroupName: /aws/directoryservice/\n\n LogsResourcePolicy:\n Type: AWS::Logs::ResourcePolicy\n Properties:\n PolicyName: DSLogSubscription\n PolicyDocument: |\n {\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ds.amazonaws.com\"},\"Action\":[\"logs:CreateLogStream\",\"logs:PutLogEvents\",\"logs:DescribeLogStreams\"],\"Resource\":\"arn:aws:logs:*:*:log-group:/aws/directoryservice/*\"}]}\n\n DirectoryLogSubscription:\n Type: AWS::DirectoryService::LogSubscription\n Properties:\n DirectoryId: # CRITICAL: target Directory Service ID to enable log forwarding\n LogGroupName: /aws/directoryservice/ # CRITICAL: CloudWatch Logs destination\n```", + "Other": "1. In the AWS Console, go to Directory Service > Directories and open your directory\n2. On the Directory details page, select the Networking & security tab\n3. In Log forwarding, click Enable\n4. Choose Create a new CloudWatch log group (or select an existing one)\n5. Click Enable to start forwarding logs", + "Terraform": "```hcl\n# Enable Directory Service log forwarding to CloudWatch Logs\nresource \"aws_cloudwatch_log_group\" \"ds\" {\n name = \"/aws/directoryservice/\"\n}\n\nresource \"aws_cloudwatch_log_resource_policy\" \"ds\" {\n policy_name = \"DSLogSubscription\"\n policy_document = jsonencode({\n Version = \"2012-10-17\",\n Statement = [{\n Effect = \"Allow\",\n Principal = { Service = \"ds.amazonaws.com\" },\n Action = [\"logs:CreateLogStream\", \"logs:PutLogEvents\", \"logs:DescribeLogStreams\"],\n Resource = \"arn:aws:logs:*:*:log-group:/aws/directoryservice/*\"\n }]\n })\n}\n\nresource \"aws_directory_service_log_subscription\" \"enable\" {\n directory_id = \"\" # CRITICAL: enables log forwarding for this directory\n log_group_name = aws_cloudwatch_log_group.ds.name # CRITICAL: CloudWatch Logs destination\n}\n```" }, "Recommendation": { - "Text": "It is recommended that that the export of logs is enabled.", - "Url": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/incident-response.html" + "Text": "Enable and maintain **log forwarding** to CloudWatch Logs.\n\n- Centralize logs in a protected group with strict access and retention\n- Apply least privilege for delivery roles and readers; prevent tampering (immutability)\n- Alert on high-risk events and aggregate across Regions/accounts for defense in depth", + "Url": "https://hub.prowler.com/check/directoryservice_directory_log_forwarding_enabled" } }, "Categories": [ diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.metadata.json index 55d1c393c0..beafbd81da 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_monitor_notifications/directoryservice_directory_monitor_notifications.metadata.json @@ -1,29 +1,37 @@ { "Provider": "aws", "CheckID": "directoryservice_directory_monitor_notifications", - "CheckTitle": "Directory Service has SNS Notifications enabled.", - "CheckType": [], + "CheckTitle": "Directory Service directory has SNS notifications enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Directory Service has SNS Notifications enabled.", - "Risk": "As a best practice, monitor status of Directory Service. This helps to avoid late actions to fix Directory Service issues.", - "RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_enable_notifications.html", + "Description": "**AWS Directory Service** directories are associated with **Amazon SNS topics** to send status change notifications (e.g., `Active` `Impaired`).\n\nThe evaluation looks for directories that have SNS event topics configured for monitoring alerts.", + "Risk": "Missing directory notifications reduces visibility into health changes, causing delayed response to `Impaired` states. This threatens availability of authentication, Kerberos/LDAP lookups, and domain joins; increases MTTR; and can enable silent replication or trust failures that impact integrity across dependent workloads.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_enable_notifications.html", + "https://support.icompaas.com/support/solutions/articles/62000233533-ensure-directory-service-has-sns-notifications-enabled" + ], "Remediation": { "Code": { - "CLI": "", + "CLI": "aws ds register-event-topic --directory-id --topic-name ", "NativeIaC": "", - "Other": "", + "Other": "1. Open AWS Console > Directory Service > Directories and select your directory\n2. Go to the Maintenance or Monitoring/Notifications section\n3. Click Actions > Create notification (or Set up notifications)\n4. Select an existing SNS topic (or create one) and Save", "Terraform": "" }, "Recommendation": { - "Text": "It is recommended set up SNS messaging to send email or text messages when the status of your directory changes.", - "Url": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_enable_notifications.html" + "Text": "Configure **AWS Directory Service** to publish directory status changes to an **SNS topic**, and subscribe your operations channels for timely alerts.\n\nApply **least privilege** on topic permissions, integrate alerts with incident response, and use **defense in depth** by pairing notifications with logs and dashboards.", + "Url": "https://hub.prowler.com/check/directoryservice_directory_monitor_notifications" } }, - "Categories": [], + "Categories": [ + "logging" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.metadata.json index 7693461252..85d6057f14 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_directory_snapshots_limit/directoryservice_directory_snapshots_limit.metadata.json @@ -1,29 +1,38 @@ { "Provider": "aws", "CheckID": "directoryservice_directory_snapshots_limit", - "CheckTitle": "Directory Service Manual Snapshots limit reached.", - "CheckType": [], + "CheckTitle": "Directory Service directory has adequate remaining manual snapshot quota", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Resource Consumption" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "Other", - "Description": "Directory Service Manual Snapshots limit reached.", - "Risk": "A limit reached can bring unwanted results. The maximum number of manual snapshots is a hard limit.", - "RelatedUrl": "https://docs.aws.amazon.com/general/latest/gr/ds_region.html", + "Description": "**AWS Directory Service** directories with **manual snapshot capacity** fully consumed or nearly exhausted, based on current snapshot count relative to the directory's maximum allowed.", + "Risk": "With no remaining snapshot capacity, you cannot create new recovery points:\n- Reduced availability during outages or ransomware\n- Higher RPO from failed scheduled backups\n- Greater change risk (schema/OS updates) without a safe rollback", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://support.icompaas.com/support/solutions/articles/62000233531--ensure-directory-service-manual-snapshots-limit-reached", + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_limits.html" + ], "Remediation": { "Code": { "CLI": "", "NativeIaC": "", - "Other": "", + "Other": "1. In the AWS Console, go to Directory Service > Directories and open \n2. Click Snapshots\n3. Select older snapshots with Type = Manual and click Delete snapshot, confirm\n4. Repeat until the number of manual snapshots is less than (manual limit - 2). For the default limit of 5, keep at most 2 manual snapshots\n5. Verify Remaining manual snapshots > 2 on the Snapshots page", "Terraform": "" }, "Recommendation": { - "Text": "Monitor manual snapshots limit to ensure capacity when you need it.", - "Url": "https://docs.aws.amazon.com/general/latest/gr/ds_region.html" + "Text": "Adopt a **snapshot lifecycle policy**: rotate/expire old manual snapshots after verifying restores, and alert on low headroom. Prefer **automated backups** for cadence and retention. Enforce **least privilege** for snapshot creation. Design operations within the *hard per-directory cap* to prevent capacity exhaustion.", + "Url": "https://hub.prowler.com/check/directoryservice_directory_snapshots_limit" } }, - "Categories": [], + "Categories": [ + "resilience" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.metadata.json index 982ab9091f..7e8510d0f3 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_ldap_certificate_expiration/directoryservice_ldap_certificate_expiration.metadata.json @@ -1,29 +1,38 @@ { "Provider": "aws", "CheckID": "directoryservice_ldap_certificate_expiration", - "CheckTitle": "Directory Service LDAP Certificates expiration.", - "CheckType": [], + "CheckTitle": "Directory Service LDAP certificate expires in more than 90 days", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Directory Service Manual Snapshots limit reached.", - "Risk": "Expired certificates can impact service availability.", - "RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_ldap.html", + "Description": "**AWS Directory Service** Secure LDAP (LDAPS) certificates are assessed for upcoming expiration by comparing each directory's certificate expiration to the current time and identifying those with `<= 90` days remaining.", + "Risk": "Expired LDAPS certificates cause TLS handshakes to fail, blocking directory binds and queries and disrupting authentication and app integrations (availability). If clients fall back to plain LDAP, credentials and directory data can be intercepted or altered (confidentiality and integrity).", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_ldap.html", + "https://support.icompaas.com/support/solutions/articles/62000229587-ensure-to-monitor-directory-service-ldap-certificates-expiration" + ], "Remediation": { "Code": { - "CLI": "", + "CLI": "aws ds register-certificate --directory-id --certificate-data file://certificate.pem", "NativeIaC": "", - "Other": "", + "Other": "1. In the AWS Console, open Directory Service and select your AWS Managed Microsoft AD ()\n2. Go to Networking & security > Secure LDAP\n3. Click Edit (Manage certificate)\n4. Choose Replace certificate (or Upload certificate)\n5. Upload a new LDAPS server certificate with private key from a trusted CA (valid for >90 days); enter the password if using a .pfx\n6. Save and wait until the certificate status is Active", "Terraform": "" }, "Recommendation": { - "Text": "Monitor certificate expiration and take automated action to alarm responsible team for taking care of the replacement or remove.", - "Url": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_ldap.html" + "Text": "Adopt certificate lifecycle management: inventory LDAPS certificates, alert well before expiry, and automate renewal with staged rollout and overlap. Enforce TLS-only LDAP and disable plaintext fallback. Apply **least privilege** and **separation of duties** to certificate issuance and deployment.", + "Url": "https://hub.prowler.com/check/directoryservice_ldap_certificate_expiration" } }, - "Categories": [], + "Categories": [ + "encryption" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.metadata.json index a3f04834e3..60ffbc26da 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_radius_server_security_protocol/directoryservice_radius_server_security_protocol.metadata.json @@ -1,29 +1,40 @@ { "Provider": "aws", "CheckID": "directoryservice_radius_server_security_protocol", - "CheckTitle": "Ensure Radius server in DS is using the recommended security protocol.", - "CheckType": [], + "CheckTitle": "Directory Service directory RADIUS server uses MS-CHAPv2", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "TTPs/Credential Access" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Ensure Radius server in DS is using the recommended security protocol.", - "Risk": "As a best practice, you might need to configure the authentication protocol between the Microsoft AD DCs and the RADIUS/MFA server. Supported protocols are PAP, CHAP MS-CHAPv1, and MS-CHAPv2. MS-CHAPv2 is recommended because it provides the strongest security of the three options.", - "RelatedUrl": "https://aws.amazon.com/blogs/security/how-to-enable-multi-factor-authentication-for-amazon-workspaces-and-amazon-quicksight-by-using-microsoft-ad-and-on-premises-credentials/", + "Description": "AWS Directory Service RADIUS configuration uses the **authentication protocol** defined for MFA integration. The finding evaluates whether directories with RADIUS enabled are set to `MS-CHAPv2` instead of weaker options like `PAP`, `CHAP`, or `MS-CHAPv1`.", + "Risk": "Using `PAP`, `CHAP`, or `MS-CHAPv1` weakens RADIUS-based MFA.\n\n`PAP` exposes cleartext credentials, while legacy CHAP variants permit offline cracking and replay, enabling unauthorized access to AD-integrated services and lateral movement, degrading confidentiality and integrity.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.secureauth.com/0903/en/ms-chapv2-and-radius--sp-initiated--for-cisco-and-netscaler-configuration-guide.html", + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_mfa.html", + "https://www.freeradius.org/documentation/freeradius-server/4.0~alpha1/raddb/mods-available/mschap.html" + ], "Remediation": { "Code": { - "CLI": "", + "CLI": "aws ds update-radius --directory-id --radius-settings AuthenticationProtocol=MS-CHAPv2", "NativeIaC": "", - "Other": "", - "Terraform": "" + "Other": "1. In the AWS Console, open Directory Service and select your directory\n2. Open the Networking & security tab (Multi-factor authentication section)\n3. Click Actions > Edit (or Enable)\n4. Set Protocol to MS-CHAPv2\n5. Click Save (or Enable) to apply", + "Terraform": "```hcl\nresource \"aws_directory_service_radius_settings\" \"\" {\n directory_id = \"\"\n radius_servers = [\"\"]\n shared_secret = \"\"\n\n authentication_protocol = \"MS-CHAPv2\" # Critical: sets the RADIUS auth protocol to MS-CHAPv2 to pass the check\n}\n```" }, "Recommendation": { - "Text": "MS-CHAPv2 provides the strongest security of the options supported, and is therefore recommended.", - "Url": "https://aws.amazon.com/blogs/security/how-to-enable-multi-factor-authentication-for-amazon-workspaces-and-amazon-quicksight-by-using-microsoft-ad-and-on-premises-credentials/" + "Text": "Standardize on `MS-CHAPv2` for RADIUS authentication to MFA providers. Disable `PAP`, `CHAP`, and `MS-CHAPv1` to prevent downgrades. Apply least privilege and defense in depth: use strong shared secrets, restrict network access to RADIUS endpoints, and monitor authentication logs for anomalies.", + "Url": "https://hub.prowler.com/check/directoryservice_radius_server_security_protocol" } }, - "Categories": [], + "Categories": [ + "identity-access" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json index a5db8db7ad..35716021fd 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json @@ -1,29 +1,40 @@ { "Provider": "aws", "CheckID": "directoryservice_supported_mfa_radius_enabled", - "CheckTitle": "Ensure Multi-Factor Authentication (MFA) using Radius Server is enabled in DS.", - "CheckType": [], + "CheckTitle": "AWS Directory Service directory has RADIUS-based MFA enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "TTPs/Initial Access", + "TTPs/Credential Access" + ], "ServiceName": "directoryservice", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:codeartifact:region:account-id:directory/directory-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Ensure Multi-Factor Authentication (MFA) using Radius Server is enabled in DS.", - "Risk": "Multi-Factor Authentication (MFA) adds an extra layer of authentication assurance beyond traditional username and password.", - "RelatedUrl": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_mfa.html", + "Description": "**AWS Directory Service directories** are evaluated for **RADIUS-backed multi-factor authentication**, confirming that MFA is configured and the RADIUS integration is active.", + "Risk": "Without **RADIUS MFA**, directory-based sign-ins to AWS-integrated services rely on a single factor, enabling credential stuffing and phishing to succeed. Compromised passwords can grant unauthorized access, drive data exfiltration, and enable privilege escalation, undermining confidentiality and integrity.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_mfa.html", + "https://support.icompaas.com/support/solutions/articles/62000233537-ensure-multi-factor-authentication-mfa-using-a-radius-server-is-enabled-in-directory-service" + ], "Remediation": { "Code": { - "CLI": "", + "CLI": "aws ds enable-radius --directory-id --radius-settings '{\"RadiusServers\":[\"\"],\"SharedSecret\":\"\"}'", "NativeIaC": "", - "Other": "", - "Terraform": "" + "Other": "1. Sign in to the AWS Console and open Directory Service\n2. Select your directory and open it\n3. Go to the Networking & security tab\n4. In Multi-factor authentication, click Actions > Enable\n5. Enter RADIUS server IP(s) and the Shared secret, then click Enable\n6. Wait until the RADIUS status shows Completed", + "Terraform": "```hcl\nresource \"aws_directory_service_radius_settings\" \"\" {\n directory_id = \"\" # Directory to enable RADIUS MFA on\n radius_servers = [\"\"] # Critical: RADIUS server endpoint(s)\n shared_secret = \"\" # Critical: Shared secret for RADIUS\n}\n```" }, "Recommendation": { - "Text": "Enabling MFA provides increased security to a user name and password as it requires the user to possess a solution that displays a time-sensitive authentication code.", - "Url": "https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_mfa.html" + "Text": "Enable and enforce **RADIUS-based MFA** for all Directory Service authentications. Apply **least privilege**, harden and monitor the RADIUS infrastructure, rotate shared secrets, and restrict network access (e.g., `UDP/1812`). Use **defense in depth** with segmentation and session controls to limit lateral movement and reduce blast radius.", + "Url": "https://hub.prowler.com/check/directoryservice_supported_mfa_radius_enabled" } }, - "Categories": [], + "Categories": [ + "identity-access" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" From 1d705e22daeff314eabd033ffd0952e690648940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 20 Oct 2025 12:29:29 +0200 Subject: [PATCH 07/57] feat(util): add from_yaml_to_json.py (#8943) --- util/compliance/ccc/from_yaml_to_json.py | 159 +++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 util/compliance/ccc/from_yaml_to_json.py diff --git a/util/compliance/ccc/from_yaml_to_json.py b/util/compliance/ccc/from_yaml_to_json.py new file mode 100644 index 0000000000..d19156d09b --- /dev/null +++ b/util/compliance/ccc/from_yaml_to_json.py @@ -0,0 +1,159 @@ +""" +Script to convert CCC security controls YAML files to JSON format. +""" + +import json +import sys +from pathlib import Path + +import yaml + + +def load_yaml(file_path): + """Load YAML file.""" + try: + with open(file_path, "r", encoding="utf-8") as file: + return yaml.safe_load(file) + except Exception as e: + print(f"Error loading YAML file: {e}") + return None + + +def transform_yaml_to_json(yaml_data): + """Transform YAML structure to JSON.""" + + result = { + "Framework": "CCC", + "Version": "", + "Provider": "", + "Description": "The best practices for Common Cloud Controls Catalog (CCC) for ", + "Requirements": [], + } + + control_families = yaml_data.get("control-families", []) + + for family in control_families: + family_name = family.get("title", "") + family_description = family.get("description", "") + controls = family.get("controls", []) + + for control in controls: + control_id = control.get("id", "") + control_title = control.get("title", "") + control_objective = control.get("objective", "") + + threat_mappings = control.get("threat-mappings", []) + guideline_mappings = control.get("guideline-mappings", []) + + assessment_reqs = control.get("assessment-requirements", []) + + for req in assessment_reqs: + req_id = req.get("id", "") + req_text = req.get("text", "").strip() + applicability = req.get("applicability", []) + recommendation = req.get("recommendation", "") + + section_threat_mappings = [] + for tm in threat_mappings: + ref_id = tm.get("reference-id", "") + entries = tm.get("entries", []) + identifiers = [] + for entry in entries: + entry_ref = entry.get("reference-id", "") + if entry_ref: + if "Core." in entry_ref: + entry_ref = entry_ref.replace("Core.", "") + identifiers.append(entry_ref) + + if identifiers: + section_threat_mappings.append( + {"ReferenceId": ref_id, "Identifiers": identifiers} + ) + + section_guideline_mappings = [] + for gm in guideline_mappings: + ref_id = gm.get("reference-id", "") + entries = gm.get("entries", []) + identifiers = [] + for entry in entries: + entry_ref = entry.get("reference-id", "") + if entry_ref: + identifiers.append(entry_ref) + + if identifiers: + section_guideline_mappings.append( + {"ReferenceId": ref_id, "Identifiers": identifiers} + ) + + checks = [] + + requirement = { + "Id": req_id, + "Description": req_text, + "Attributes": [ + { + "FamilyName": family_name, + "FamilyDescription": family_description, + "Section": f"{control_id} {control_title}", + "SubSection": "", + "SubSectionObjective": control_objective.strip(), + "Applicability": applicability, + "Recommendation": recommendation, + "SectionThreatMappings": section_threat_mappings, + "SectionGuidelineMappings": section_guideline_mappings, + } + ], + "Checks": checks, + } + + result["Requirements"].append(requirement) + + return result + + +def save_json(data, file_path): + """Save data as JSON.""" + try: + with open(file_path, "w", encoding="utf-8") as file: + json.dump(data, file, indent=2, ensure_ascii=False) + return True + except Exception as e: + print(f"Error saving JSON file: {e}") + return False + + +def main(): + """Main function.""" + if len(sys.argv) < 2: + print("Usage: python from_yaml_to_json.py [output_file.json]") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else "output.json" + + if not Path(input_file).exists(): + print(f"Error: File {input_file} does not exist.") + sys.exit(1) + + print(f"Loading {input_file}...") + yaml_data = load_yaml(input_file) + + if yaml_data is None: + print("Could not load YAML file.") + sys.exit(1) + + print("Transforming YAML to JSON...") + json_data = transform_yaml_to_json(yaml_data) + + print(f"Saving result to {output_file}...") + if save_json(json_data, output_file): + print("Conversion completed successfully!") + print(f"Generated file: {output_file}") + print(f"Total requirements processed: {len(json_data['Requirements'])}") + else: + print("Error saving JSON file.") + sys.exit(1) + + +if __name__ == "__main__": + main() From 985d73f44fec30301d83757945140ffb19d51272 Mon Sep 17 00:00:00 2001 From: StylusFrost <43682773+StylusFrost@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:45:20 +0200 Subject: [PATCH 08/57] test(ui): enhance Playwright test setups for user authentication (#8881) Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> --- ui/.gitignore | 3 +- ui/package.json | 8 +- ui/playwright.config.ts | 66 ++++ ui/tests/helpers.ts | 24 ++ ui/tests/page-objects/home-page.ts | 125 +++++++ ui/tests/page-objects/sign-in-page.ts | 316 ++++++++++++++++++ ui/tests/setups/admin.auth.setup.ts | 15 + .../invite-and-manage-users.auth.setup.ts | 15 + ui/tests/setups/manage-account.auth.setup.ts | 15 + .../manage-cloud-providers.auth.setup.ts | 15 + .../setups/manage-integrations.auth.setup.ts | 15 + ui/tests/setups/manage-scans.auth.setup.ts | 15 + .../setups/unlimited-visibility.auth.setup.ts | 15 + 13 files changed, 642 insertions(+), 5 deletions(-) create mode 100644 ui/tests/page-objects/home-page.ts create mode 100644 ui/tests/page-objects/sign-in-page.ts create mode 100644 ui/tests/setups/admin.auth.setup.ts create mode 100644 ui/tests/setups/invite-and-manage-users.auth.setup.ts create mode 100644 ui/tests/setups/manage-account.auth.setup.ts create mode 100644 ui/tests/setups/manage-cloud-providers.auth.setup.ts create mode 100644 ui/tests/setups/manage-integrations.auth.setup.ts create mode 100644 ui/tests/setups/manage-scans.auth.setup.ts create mode 100644 ui/tests/setups/unlimited-visibility.auth.setup.ts diff --git a/ui/.gitignore b/ui/.gitignore index 555ceeb8d3..3b905e64d8 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -33,4 +33,5 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts +playwright/.auth diff --git a/ui/package.json b/ui/package.json index ea80ad0f32..e0ec108dfc 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,10 +15,10 @@ "format:check": "./node_modules/.bin/prettier --check ./app", "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app", "prepare": "husky", - "test:e2e": "playwright test", - "test:e2e:ui": "playwright test --ui", - "test:e2e:debug": "playwright test --debug", - "test:e2e:headed": "playwright test --headed", + "test:e2e": "playwright test --project=chromium", + "test:e2e:ui": "playwright test --project=chromium --ui", + "test:e2e:debug": "playwright test --project=chromium --debug", + "test:e2e:headed": "playwright test --project=chromium --headed", "test:e2e:report": "playwright show-report", "test:e2e:install": "playwright install" }, diff --git a/ui/playwright.config.ts b/ui/playwright.config.ts index a680feabdb..5bea00bfe7 100644 --- a/ui/playwright.config.ts +++ b/ui/playwright.config.ts @@ -20,6 +20,72 @@ export default defineConfig({ }, projects: [ + // =========================================== + // Authentication Setup Projects + // =========================================== + // These projects handle user authentication for different permission levels + // Each setup creates authenticated state files that can be reused by test suites + + // Admin user authentication setup + // Creates authenticated state for admin users with full system permissions + { + name: "admin.auth.setup", + testMatch: "admin.auth.setup.ts", + }, + + // Scans management user authentication setup + // Creates authenticated state for users with scan management permissions + { + name: "manage-scans.auth.setup", + testMatch: "manage-scans.auth.setup.ts", + }, + + // Integrations management user authentication setup + // Creates authenticated state for users with integration management permissions + { + name: "manage-integrations.auth.setup", + testMatch: "manage-integrations.auth.setup.ts", + }, + + // Account management user authentication setup + // Creates authenticated state for users with account management permissions + { + name: "manage-account.auth.setup", + testMatch: "manage-account.auth.setup.ts", + }, + + // Cloud providers management user authentication setup + // Creates authenticated state for users with cloud provider management permissions + { + name: "manage-cloud-providers.auth.setup", + testMatch: "manage-cloud-providers.auth.setup.ts", + }, + + // Unlimited visibility user authentication setup + // Creates authenticated state for users with unlimited visibility permissions + { + name: "unlimited-visibility.auth.setup", + testMatch: "unlimited-visibility.auth.setup.ts", + }, + + // Invite and manage users authentication setup + // Creates authenticated state for users with user invitation and management permissions + { + name: "invite-and-manage-users.auth.setup", + testMatch: "invite-and-manage-users.auth.setup.ts", + }, + + // All authentication setups combined + // Runs all authentication setup files to create all user states + { + name: "all.auth.setup", + testMatch: "**/*.auth.setup.ts", + }, + + // =========================================== + // Test Suite Projects + // =========================================== + // These projects run the actual test suites { name: "chromium", use: { ...devices["Desktop Chrome"] }, diff --git a/ui/tests/helpers.ts b/ui/tests/helpers.ts index 9f5efb0fdf..db9c6facbb 100644 --- a/ui/tests/helpers.ts +++ b/ui/tests/helpers.ts @@ -1,4 +1,5 @@ import { Page, expect } from "@playwright/test"; +import { SignInPage, SignInCredentials } from "./page-objects/sign-in-page"; export const ERROR_MESSAGES = { INVALID_CREDENTIALS: "Invalid email or password", @@ -138,6 +139,29 @@ export async function verifyDashboardRoute(page: Page) { await expect(page).toHaveURL("/"); } +export async function authenticateAndSaveState( + page: Page, + email: string, + password: string, + storagePath: string, +) { + if (!email || !password) { + throw new Error('Email and password are required for authentication and save state'); + } + + // Create SignInPage instance + const signInPage = new SignInPage(page); + const credentials: SignInCredentials = { email, password }; + + // Perform authentication steps using Page Object Model + await signInPage.goto(); + await signInPage.login(credentials); + await signInPage.verifySuccessfulLogin(); + + // Save authentication state + await page.context().storageState({ path: storagePath }); +} + export async function getSession(page: Page) { const response = await page.request.get("/api/auth/session"); return response.json(); diff --git a/ui/tests/page-objects/home-page.ts b/ui/tests/page-objects/home-page.ts new file mode 100644 index 0000000000..077591eb4e --- /dev/null +++ b/ui/tests/page-objects/home-page.ts @@ -0,0 +1,125 @@ +import { Page, Locator, expect } from "@playwright/test"; + +export class HomePage { + readonly page: Page; + + // Main content elements + readonly mainContent: Locator; + readonly breadcrumbs: Locator; + readonly overviewHeading: Locator; + + // Navigation elements + readonly navigationMenu: Locator; + readonly userMenu: Locator; + readonly signOutButton: Locator; + + // Dashboard elements + readonly dashboardCards: Locator; + readonly overviewSection: Locator; + + // UI elements + readonly themeToggle: Locator; + readonly logo: Locator; + + constructor(page: Page) { + this.page = page; + + // Main content elements + this.mainContent = page.locator("main"); + this.breadcrumbs = page.getByLabel("Breadcrumbs"); + this.overviewHeading = page.getByRole("heading", { name: "Overview", exact: true }); + + // Navigation elements + this.navigationMenu = page.locator("nav"); + this.userMenu = page.getByRole("button", { name: /user menu/i }); + this.signOutButton = page.getByRole("button", { name: "Sign out" }); + + // Dashboard elements + this.dashboardCards = page.locator('[data-testid="dashboard-card"]'); + this.overviewSection = page.locator('[data-testid="overview-section"]'); + + // UI elements + this.themeToggle = page.getByLabel("Toggle theme"); + this.logo = page.locator('svg[width="300"]'); + } + + // Navigation methods + async goto(): Promise { + await this.page.goto("/"); + await this.waitForPageLoad(); + } + + async waitForPageLoad(): Promise { + await this.page.waitForLoadState("networkidle"); + } + + // Verification methods + async verifyPageLoaded(): Promise { + await expect(this.page).toHaveURL("/"); + await expect(this.mainContent).toBeVisible(); + await expect(this.overviewHeading).toBeVisible(); + await this.waitForPageLoad(); + } + + async verifyBreadcrumbs(): Promise { + await expect(this.breadcrumbs).toBeVisible(); + await expect(this.overviewHeading).toBeVisible(); + } + + async verifyMainContent(): Promise { + await expect(this.mainContent).toBeVisible(); + } + + // Navigation methods + async navigateToOverview(): Promise { + await this.overviewHeading.click(); + } + + async openUserMenu(): Promise { + await this.userMenu.click(); + } + + async signOut(): Promise { + await this.openUserMenu(); + await this.signOutButton.click(); + } + + // Dashboard methods + async verifyDashboardCards(): Promise { + await expect(this.dashboardCards.first()).toBeVisible(); + } + + async verifyOverviewSection(): Promise { + await expect(this.overviewSection).toBeVisible(); + } + + // Utility methods + async refresh(): Promise { + await this.page.reload(); + await this.waitForPageLoad(); + } + + async goBack(): Promise { + await this.page.goBack(); + await this.waitForPageLoad(); + } + + // Accessibility methods + async verifyKeyboardNavigation(): Promise { + // Test tab navigation through main elements + await this.page.keyboard.press("Tab"); + await expect(this.themeToggle).toBeFocused(); + } + + // Wait methods + async waitForRedirect(expectedUrl: string): Promise { + await this.page.waitForURL(expectedUrl); + } + + async waitForContentLoad(): Promise { + await this.page.waitForFunction(() => { + const main = document.querySelector("main"); + return main && main.offsetHeight > 0; + }); + } +} diff --git a/ui/tests/page-objects/sign-in-page.ts b/ui/tests/page-objects/sign-in-page.ts new file mode 100644 index 0000000000..2c426606c7 --- /dev/null +++ b/ui/tests/page-objects/sign-in-page.ts @@ -0,0 +1,316 @@ +import { Page, Locator, expect } from "@playwright/test"; +import { HomePage } from "./home-page"; + +export interface SignInCredentials { + email: string; + password: string; +} + +export interface SocialAuthConfig { + googleEnabled: boolean; + githubEnabled: boolean; +} + +export class SignInPage { + readonly page: Page; + readonly homePage: HomePage; + + // Form elements + readonly emailInput: Locator; + readonly passwordInput: Locator; + readonly loginButton: Locator; + readonly form: Locator; + + // Social authentication buttons + readonly googleButton: Locator; + readonly githubButton: Locator; + readonly samlButton: Locator; + + // Navigation elements + readonly signUpLink: Locator; + readonly backButton: Locator; + + // UI elements + readonly title: Locator; + readonly logo: Locator; + readonly themeToggle: Locator; + + // Error messages + readonly errorMessages: Locator; + readonly loadingIndicator: Locator; + + // SAML specific elements + readonly samlModeTitle: Locator; + readonly samlEmailInput: Locator; + + constructor(page: Page) { + this.page = page; + this.homePage = new HomePage(page); + + // Form elements + this.emailInput = page.getByLabel("Email"); + this.passwordInput = page.getByLabel("Password"); + this.loginButton = page.getByRole("button", { name: "Log in" }); + this.form = page.locator("form"); + + // Social authentication buttons + this.googleButton = page.getByText("Continue with Google"); + this.githubButton = page.getByText("Continue with Github"); + this.samlButton = page.getByText("Continue with SAML SSO"); + + // Navigation elements + this.signUpLink = page.getByRole("link", { name: "Sign up" }); + this.backButton = page.getByText("Back"); + + // UI elements + this.title = page.getByText("Sign in", { exact: true }); + this.logo = page.locator('svg[width="300"]'); + this.themeToggle = page.getByLabel("Toggle theme"); + + // Error messages + this.errorMessages = page.locator('[role="alert"], .error-message, [data-testid="error"]'); + this.loadingIndicator = page.getByText("Loading"); + + // SAML specific elements + this.samlModeTitle = page.getByText("Sign in with SAML SSO"); + this.samlEmailInput = page.getByLabel("Email"); + } + + // Navigation methods + async goto(): Promise { + await this.page.goto("/sign-in"); + await this.waitForPageLoad(); + } + + async waitForPageLoad(): Promise { + await this.page.waitForLoadState("networkidle"); + } + + // Form interaction methods + async fillEmail(email: string): Promise { + await this.emailInput.fill(email); + } + + async fillPassword(password: string): Promise { + await this.passwordInput.fill(password); + } + + async fillCredentials(credentials: SignInCredentials): Promise { + await this.fillEmail(credentials.email); + await this.fillPassword(credentials.password); + } + + async submitForm(): Promise { + await this.loginButton.click(); + } + + async login(credentials: SignInCredentials): Promise { + await this.fillCredentials(credentials); + await this.submitForm(); + } + + // Social authentication methods + async clickGoogleAuth(): Promise { + await this.googleButton.click(); + } + + async clickGithubAuth(): Promise { + await this.githubButton.click(); + } + + async clickSamlAuth(): Promise { + await this.samlButton.click(); + } + + // SAML SSO methods + async toggleSamlMode(): Promise { + await this.clickSamlAuth(); + } + + async goBackFromSaml(): Promise { + await this.backButton.click(); + } + + async fillSamlEmail(email: string): Promise { + await this.samlEmailInput.fill(email); + } + + async submitSamlForm(): Promise { + await this.submitForm(); + } + + // Navigation methods + async goToSignUp(): Promise { + await this.signUpLink.click(); + } + + // Validation and assertion methods + async verifyPageLoaded(): Promise { + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.logo).toBeVisible(); + await expect(this.title).toBeVisible(); + await this.waitForPageLoad(); + } + + async verifyFormElements(): Promise { + await expect(this.emailInput).toBeVisible(); + await expect(this.passwordInput).toBeVisible(); + await expect(this.loginButton).toBeVisible(); + } + + async verifySocialButtons(config: SocialAuthConfig): Promise { + if (config.googleEnabled) { + await expect(this.googleButton).toBeVisible(); + } + if (config.githubEnabled) { + await expect(this.githubButton).toBeVisible(); + } + await expect(this.samlButton).toBeVisible(); + } + + async verifyNavigationLinks(): Promise { + await expect(this.page.getByText("Need to create an account?")).toBeVisible(); + await expect(this.signUpLink).toBeVisible(); + } + + async verifySuccessfulLogin(): Promise { + await this.homePage.verifyPageLoaded(); + } + + async verifyLoginError(errorMessage: string = "Invalid email or password"): Promise { + await expect(this.page.getByText(errorMessage).first()).toBeVisible(); + await expect(this.page).toHaveURL("/sign-in"); + } + + async verifySamlModeActive(): Promise { + await expect(this.samlModeTitle).toBeVisible(); + await expect(this.passwordInput).not.toBeVisible(); + await expect(this.backButton).toBeVisible(); + } + + async verifyNormalModeActive(): Promise { + await expect(this.title).toBeVisible(); + await expect(this.passwordInput).toBeVisible(); + } + + async verifyLoadingState(): Promise { + await expect(this.loginButton).toHaveAttribute("aria-disabled", "true"); + await expect(this.loadingIndicator).toBeVisible(); + } + + async verifyFormValidation(): Promise { + // Check for common validation messages + const emailError = this.page.getByText("Please enter a valid email address."); + const passwordError = this.page.getByText("Password is required."); + + // At least one validation error should be visible + await expect(emailError.or(passwordError)).toBeVisible(); + } + + // Accessibility methods + async verifyKeyboardNavigation(): Promise { + // Test tab navigation through form elements + await this.page.keyboard.press("Tab"); // Theme toggle + await this.page.keyboard.press("Tab"); // Email field + await expect(this.emailInput).toBeFocused(); + + await this.page.keyboard.press("Tab"); // Password field + await expect(this.passwordInput).toBeFocused(); + + await this.page.keyboard.press("Tab"); // Show password button + await this.page.keyboard.press("Tab"); // Login button + await expect(this.loginButton).toBeFocused(); + } + + async verifyAriaLabels(): Promise { + await expect(this.page.getByRole("textbox", { name: "Email" })).toBeVisible(); + await expect(this.page.getByRole("textbox", { name: "Password" })).toBeVisible(); + await expect(this.page.getByRole("button", { name: "Log in" })).toBeVisible(); + } + + // Utility methods + async clearForm(): Promise { + await this.emailInput.clear(); + await this.passwordInput.clear(); + } + + async isFormValid(): Promise { + const emailValue = await this.emailInput.inputValue(); + const passwordValue = await this.passwordInput.inputValue(); + return emailValue.length > 0 && passwordValue.length > 0; + } + + async getFormErrors(): Promise { + const errorElements = await this.errorMessages.all(); + const errors: string[] = []; + + for (const element of errorElements) { + const text = await element.textContent(); + if (text) { + errors.push(text.trim()); + } + } + + return errors; + } + + // Browser interaction methods + async refresh(): Promise { + await this.page.reload(); + await this.waitForPageLoad(); + } + + async goBack(): Promise { + await this.page.goBack(); + await this.waitForPageLoad(); + } + + // Session management methods + async logout(): Promise { + await this.homePage.signOut(); + } + + async verifyLogoutSuccess(): Promise { + await expect(this.page).toHaveURL("/sign-in"); + await expect(this.title).toBeVisible(); + } + + // Advanced interaction methods + async fillFormWithValidation(credentials: SignInCredentials): Promise { + // Fill email first and check for validation + await this.fillEmail(credentials.email); + await this.page.keyboard.press("Tab"); // Trigger validation + + // Fill password + await this.fillPassword(credentials.password); + } + + async submitFormWithEnterKey(): Promise { + await this.passwordInput.press("Enter"); + } + + async submitFormWithButtonClick(): Promise { + await this.submitForm(); + } + + // Error handling methods + async handleSamlError(): Promise { + const samlError = this.page.getByText("SAML Authentication Error"); + if (await samlError.isVisible()) { + // Handle SAML error if present + console.log("SAML authentication error detected"); + } + } + + // Wait methods + async waitForFormSubmission(): Promise { + await this.page.waitForFunction(() => { + const button = document.querySelector('button[aria-disabled="true"]'); + return button === null; + }); + } + + async waitForRedirect(expectedUrl: string): Promise { + await this.page.waitForURL(expectedUrl); + } +} diff --git a/ui/tests/setups/admin.auth.setup.ts b/ui/tests/setups/admin.auth.setup.ts new file mode 100644 index 0000000000..0e24c242c3 --- /dev/null +++ b/ui/tests/setups/admin.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authAdminSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const adminUserFile = 'playwright/.auth/admin_user.json'; + +authAdminSetup('authenticate as admin e2e user', async ({ page }) => { + const adminEmail = process.env.E2E_ADMIN_USER; + const adminPassword = process.env.E2E_ADMIN_PASSWORD; + + if (!adminEmail || !adminPassword) { + throw new Error('E2E_ADMIN_USER and E2E_ADMIN_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, adminEmail, adminPassword, adminUserFile); +}); \ No newline at end of file diff --git a/ui/tests/setups/invite-and-manage-users.auth.setup.ts b/ui/tests/setups/invite-and-manage-users.auth.setup.ts new file mode 100644 index 0000000000..e11eecdaa3 --- /dev/null +++ b/ui/tests/setups/invite-and-manage-users.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authInviteAndManageUsersSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const inviteAndManageUsersUserFile = 'playwright/.auth/invite_and_manage_users_user.json'; + +authInviteAndManageUsersSetup('authenticate as invite and manage users e2e user', async ({ page }) => { + const inviteAndManageUsersEmail = process.env.E2E_INVITE_AND_MANAGE_USERS_USER; + const inviteAndManageUsersPassword = process.env.E2E_INVITE_AND_MANAGE_USERS_PASSWORD; + + if (!inviteAndManageUsersEmail || !inviteAndManageUsersPassword) { + throw new Error('E2E_INVITE_AND_MANAGE_USERS_USER and E2E_INVITE_AND_MANAGE_USERS_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, inviteAndManageUsersEmail, inviteAndManageUsersPassword, inviteAndManageUsersUserFile); +}); diff --git a/ui/tests/setups/manage-account.auth.setup.ts b/ui/tests/setups/manage-account.auth.setup.ts new file mode 100644 index 0000000000..4fe5b7b5a2 --- /dev/null +++ b/ui/tests/setups/manage-account.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authManageAccountSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const manageAccountUserFile = 'playwright/.auth/manage_account_user.json'; + +authManageAccountSetup('authenticate as manage account e2e user', async ({ page }) => { + const accountEmail = process.env.E2E_MANAGE_ACCOUNT_USER; + const accountPassword = process.env.E2E_MANAGE_ACCOUNT_PASSWORD; + + if (!accountEmail || !accountPassword) { + throw new Error('E2E_MANAGE_ACCOUNT_USER and E2E_MANAGE_ACCOUNT_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, accountEmail, accountPassword, manageAccountUserFile); +}); diff --git a/ui/tests/setups/manage-cloud-providers.auth.setup.ts b/ui/tests/setups/manage-cloud-providers.auth.setup.ts new file mode 100644 index 0000000000..205e2b50e1 --- /dev/null +++ b/ui/tests/setups/manage-cloud-providers.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authManageCloudProvidersSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const manageCloudProvidersUserFile = 'playwright/.auth/manage_cloud_providers_user.json'; + +authManageCloudProvidersSetup('authenticate as manage cloud providers e2e user', async ({ page }) => { + const cloudProvidersEmail = process.env.E2E_MANAGE_CLOUD_PROVIDERS_USER; + const cloudProvidersPassword = process.env.E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD; + + if (!cloudProvidersEmail || !cloudProvidersPassword) { + throw new Error('E2E_MANAGE_CLOUD_PROVIDERS_USER and E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, cloudProvidersEmail, cloudProvidersPassword, manageCloudProvidersUserFile); +}); diff --git a/ui/tests/setups/manage-integrations.auth.setup.ts b/ui/tests/setups/manage-integrations.auth.setup.ts new file mode 100644 index 0000000000..fb98e4a157 --- /dev/null +++ b/ui/tests/setups/manage-integrations.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authManageIntegrationsSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const manageIntegrationsUserFile = 'playwright/.auth/manage_integrations_user.json'; + +authManageIntegrationsSetup('authenticate as integrations e2e user', async ({ page }) => { + const integrationsEmail = process.env.E2E_MANAGE_INTEGRATIONS_USER; + const integrationsPassword = process.env.E2E_MANAGE_INTEGRATIONS_PASSWORD; + + if (!integrationsEmail || !integrationsPassword) { + throw new Error('E2E_MANAGE_INTEGRATIONS_USER and E2E_MANAGE_INTEGRATIONS_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, integrationsEmail, integrationsPassword, manageIntegrationsUserFile); +}); diff --git a/ui/tests/setups/manage-scans.auth.setup.ts b/ui/tests/setups/manage-scans.auth.setup.ts new file mode 100644 index 0000000000..7a8f7e95e2 --- /dev/null +++ b/ui/tests/setups/manage-scans.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authManageScansSetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const manageScansUserFile = 'playwright/.auth/manage_scans_user.json'; + +authManageScansSetup('authenticate as scans e2e user', async ({ page }) => { + const scansEmail = process.env.E2E_MANAGE_SCANS_USER; + const scansPassword = process.env.E2E_MANAGE_SCANS_PASSWORD; + + if (!scansEmail || !scansPassword) { + throw new Error('E2E_MANAGE_SCANS_USER and E2E_MANAGE_SCANS_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, scansEmail, scansPassword, manageScansUserFile); +}); diff --git a/ui/tests/setups/unlimited-visibility.auth.setup.ts b/ui/tests/setups/unlimited-visibility.auth.setup.ts new file mode 100644 index 0000000000..533158ec70 --- /dev/null +++ b/ui/tests/setups/unlimited-visibility.auth.setup.ts @@ -0,0 +1,15 @@ +import { test as authUnlimitedVisibilitySetup } from '@playwright/test'; +import { authenticateAndSaveState } from '@/tests/helpers'; + +const unlimitedVisibilityUserFile = 'playwright/.auth/unlimited_visibility_user.json'; + +authUnlimitedVisibilitySetup('authenticate as unlimited visibility e2e user', async ({ page }) => { + const unlimitedVisibilityEmail = process.env.E2E_UNLIMITED_VISIBILITY_USER; + const unlimitedVisibilityPassword = process.env.E2E_UNLIMITED_VISIBILITY_PASSWORD; + + if (!unlimitedVisibilityEmail || !unlimitedVisibilityPassword) { + throw new Error('E2E_UNLIMITED_VISIBILITY_USER and E2E_UNLIMITED_VISIBILITY_PASSWORD environment variables are required'); + } + + await authenticateAndSaveState(page, unlimitedVisibilityEmail, unlimitedVisibilityPassword, unlimitedVisibilityUserFile); +}); From 0b9969a7234a9565082ddcb97cb9183d4fb0e40e Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:51:11 +0200 Subject: [PATCH 09/57] feat: update M365 credentials form (#8929) Co-authored-by: HugoPBrito --- ui/CHANGELOG.md | 1 + .../add-credentials/page.tsx | 2 + .../providers/credentials-update-info.tsx | 4 + .../workflow/forms/base-credentials-form.tsx | 38 ++++-- .../m365/credentials-type/index.ts | 2 + .../m365-certificate-credentials-form.tsx | 72 ++++++++++++ .../m365-client-secret-credentials-form.tsx | 58 ++++++++++ .../select-credentials-type/m365/index.ts | 5 + ...o-group-m365-via-credentials-type-form.tsx | 72 ++++++++++++ .../m365/select-via-m365.tsx | 38 ++++++ .../workflow/forms/via-credentials/index.ts | 1 - .../via-credentials/m365-credentials-form.tsx | 109 ------------------ ui/hooks/use-credentials-form.ts | 37 ++++-- .../build-crendentials.ts | 17 ++- .../provider-credential-fields.ts | 2 + ui/lib/provider-helpers.ts | 12 +- ui/types/components.ts | 15 ++- ui/types/formSchemas.ts | 56 ++++----- 18 files changed, 382 insertions(+), 159 deletions(-) create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx delete mode 100644 ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 72c660c788..9f0174f4ba 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to the **Prowler UI** are documented in this file. - API key management in user profile [(#8308)](https://github.com/prowler-cloud/prowler/pull/8308) - Refresh access token error handling [(#8864)](https://github.com/prowler-cloud/prowler/pull/8864) - Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) +- New M365 credentials certificate authentication method [(#8929)](https://github.com/prowler-cloud/prowler/pull/8929) ### 🔄 Changed diff --git a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx index 1c039886b9..3490cdb792 100644 --- a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx +++ b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx @@ -10,6 +10,7 @@ import { SelectViaGCP, } from "@/components/providers/workflow/forms/select-credentials-type/gcp"; import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github"; +import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365"; import { getProviderFormType } from "@/lib/provider-helpers"; import { ProviderType } from "@/types/providers"; @@ -28,6 +29,7 @@ export default async function AddCredentialsPage({ searchParams }: Props) { if (providerType === "gcp") return ; if (providerType === "github") return ; + if (providerType === "m365") return ; return null; case "credentials": diff --git a/ui/components/providers/credentials-update-info.tsx b/ui/components/providers/credentials-update-info.tsx index 0fe75f0cb2..3fb13f97a7 100644 --- a/ui/components/providers/credentials-update-info.tsx +++ b/ui/components/providers/credentials-update-info.tsx @@ -3,6 +3,7 @@ import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws"; import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp"; import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github"; +import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365"; import { ProviderType } from "@/types/providers"; interface UpdateCredentialsInfoProps { @@ -24,6 +25,9 @@ export const CredentialsUpdateInfo = ({ if (providerType === "github") { return ; } + if (providerType === "m365") { + return ; + } return null; }; diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 9c66103e1d..a50cfdeb74 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -17,7 +17,8 @@ import { GCPDefaultCredentials, GCPServiceAccountKey, KubernetesCredentials, - M365Credentials, + M365CertificateCredentials, + M365ClientSecretCredentials, ProviderType, } from "@/types"; @@ -26,10 +27,13 @@ import { AWSStaticCredentialsForm } from "./select-credentials-type/aws/credenti import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credentials-type/aws-role-credentials-form"; import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type"; import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form"; +import { + M365CertificateCredentialsForm, + M365ClientSecretCredentialsForm, +} from "./select-credentials-type/m365"; import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form"; -import { M365CredentialsForm } from "./via-credentials/m365-credentials-form"; type BaseCredentialsFormProps = { providerType: ProviderType; @@ -103,11 +107,22 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} - {providerType === "m365" && ( - } - /> - )} + {providerType === "m365" && + searchParamsObj.get("via") === "app_client_secret" && ( + + } + /> + )} + {providerType === "m365" && + searchParamsObj.get("via") === "app_certificate" && ( + + } + /> + )} {providerType === "gcp" && searchParamsObj.get("via") === "service-account" && ( } + onPress={(e) => { + const formElement = e.target as HTMLElement; + const form = formElement.closest("form"); + if (form) { + form.dispatchEvent( + new Event("submit", { bubbles: true, cancelable: true }), + ); + } + }} > {isLoading ? <>Loading : {submitButtonText}} diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts new file mode 100644 index 0000000000..91d6766bac --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts @@ -0,0 +1,2 @@ +export { M365CertificateCredentialsForm } from "./m365-certificate-credentials-form"; +export { M365ClientSecretCredentialsForm } from "./m365-client-secret-credentials-form"; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx new file mode 100644 index 0000000000..03dd5a72e0 --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { Control } from "react-hook-form"; + +import { CustomInput, CustomTextarea } from "@/components/ui/custom"; +import { CustomLink } from "@/components/ui/custom/custom-link"; +import { M365CertificateCredentials } from "@/types"; + +export const M365CertificateCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ App Certificate Credentials +
+
+ Please provide your Microsoft 365 application credentials with + certificate authentication. +
+
+ + + +

+ The certificate content must be base64 encoded from an unsigned + certificate. For detailed instructions on how to generate and encode + your certificate, please refer to the{" "} + + certificate generation guide + + . +

+ + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx new file mode 100644 index 0000000000..3312fb536a --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { Control } from "react-hook-form"; + +import { CustomInput } from "@/components/ui/custom"; +import { M365ClientSecretCredentials } from "@/types"; + +export const M365ClientSecretCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ App Client Secret Credentials +
+
+ Please provide your Microsoft 365 application credentials. +
+
+ + + + + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts new file mode 100644 index 0000000000..84610f510a --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts @@ -0,0 +1,5 @@ +export { + M365CertificateCredentialsForm, + M365ClientSecretCredentialsForm, +} from "./credentials-type"; +export { SelectViaM365 } from "./select-via-m365"; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx new file mode 100644 index 0000000000..2f5599363b --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { RadioGroup } from "@heroui/radio"; +import React from "react"; +import { Control, Controller } from "react-hook-form"; + +import { CustomRadio } from "@/components/ui/custom"; +import { FormMessage } from "@/components/ui/form"; + +type RadioGroupM365ViaCredentialsFormProps = { + control: Control; + isInvalid: boolean; + errorMessage?: string; + onChange?: (value: string) => void; +}; + +export const RadioGroupM365ViaCredentialsTypeForm = ({ + control, + isInvalid, + errorMessage, + onChange, +}: RadioGroupM365ViaCredentialsFormProps) => { + return ( + ( + <> + { + field.onChange(value); + if (onChange) { + onChange(value); + } + }} + > +
+ + Select Authentication Method + + +
+ App Client Secret Credentials +
+
+ +
+ App Certificate Credentials +
+
+
+
+ {errorMessage && ( + + {errorMessage} + + )} + + )} + /> + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx new file mode 100644 index 0000000000..020e4ed0e1 --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; + +import { Form } from "@/components/ui/form"; + +import { RadioGroupM365ViaCredentialsTypeForm } from "./radio-group-m365-via-credentials-type-form"; + +interface SelectViaM365Props { + initialVia?: string; +} + +export const SelectViaM365 = ({ initialVia }: SelectViaM365Props) => { + const router = useRouter(); + const form = useForm({ + defaultValues: { + m365CredentialsType: initialVia || "", + }, + }); + + const handleSelectionChange = (value: string) => { + const url = new URL(window.location.href); + url.searchParams.set("via", value); + router.push(url.toString()); + }; + + return ( +
+ + + ); +}; diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts index d020b9715f..982e5ca701 100644 --- a/ui/components/providers/workflow/forms/via-credentials/index.ts +++ b/ui/components/providers/workflow/forms/via-credentials/index.ts @@ -1,4 +1,3 @@ export * from "./azure-credentials-form"; export * from "./github-credentials-form"; export * from "./k8s-credentials-form"; -export * from "./m365-credentials-form"; diff --git a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx deleted file mode 100644 index 1659a503e8..0000000000 --- a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Control } from "react-hook-form"; - -import { InfoIcon } from "@/components/icons"; -import { CustomInput } from "@/components/ui/custom"; -import { CustomLink } from "@/components/ui/custom/custom-link"; -import { M365Credentials } from "@/types"; - -export const M365CredentialsForm = ({ - control, -}: { - control: Control; -}) => { - return ( - <> -
-
- Connect via Credentials -
-
- Please provide the information for your Microsoft 365 credentials. -
-
- - - -

- {" "} - User and password authentication is being deprecated due to - Microsoft's on-going MFA enforcement across all tenants (see{" "} - - Microsoft docs - - ). -

- -
- -

- By October 2025, MFA will be mandatory. -

-
-

- Due to that change, you must only{" "} - - use application authentication - {" "} - to maintain all Prowler M365 scan capabilities. -

- - - - ); -}; diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts index 268d714d71..2690b9307a 100644 --- a/ui/hooks/use-credentials-form.ts +++ b/ui/hooks/use-credentials-form.ts @@ -40,8 +40,8 @@ export const useCredentialsForm = ({ if (providerType === "gcp" && via === "service-account") { return addCredentialsServiceAccountFormSchema(providerType); } - // For GitHub, we need to pass the via parameter to determine which fields are required - if (providerType === "github") { + // For GitHub and M365, we need to pass the via parameter to determine which fields are required + if (providerType === "github" || providerType === "m365") { return addCredentialsFormSchema(providerType, via); } return addCredentialsFormSchema(providerType); @@ -99,13 +99,27 @@ export const useCredentialsForm = ({ [ProviderCredentialFields.TENANT_ID]: "", }; case "m365": + // M365 credentials based on via parameter + if (via === "app_client_secret") { + return { + ...baseDefaults, + [ProviderCredentialFields.CLIENT_ID]: "", + [ProviderCredentialFields.CLIENT_SECRET]: "", + [ProviderCredentialFields.TENANT_ID]: "", + }; + } + if (via === "app_certificate") { + return { + ...baseDefaults, + [ProviderCredentialFields.CLIENT_ID]: "", + [ProviderCredentialFields.CERTIFICATE_CONTENT]: "", + [ProviderCredentialFields.TENANT_ID]: "", + }; + } return { ...baseDefaults, [ProviderCredentialFields.CLIENT_ID]: "", - [ProviderCredentialFields.CLIENT_SECRET]: "", [ProviderCredentialFields.TENANT_ID]: "", - [ProviderCredentialFields.USER]: "", - [ProviderCredentialFields.PASSWORD]: "", }; case "gcp": return { @@ -146,9 +160,14 @@ export const useCredentialsForm = ({ } }; + const defaultValues = getDefaultValues(); + const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: getDefaultValues(), + defaultValues: defaultValues, + mode: "onSubmit", + reValidateMode: "onChange", + criteriaMode: "all", // Show all errors for each field }); const { handleServerResponse } = useFormServerErrors( @@ -169,6 +188,7 @@ export const useCredentialsForm = ({ // Filter out empty values first, then append all remaining values const filteredValues = filterEmptyValues(values); + Object.entries(filteredValues).forEach(([key, value]) => { formData.append(key, value); }); @@ -181,9 +201,12 @@ export const useCredentialsForm = ({ } }; + const { isSubmitting, errors } = form.formState; + return { form, - isLoading: form.formState.isSubmitting, + isLoading: isSubmitting, + errors, handleSubmit, handleBackStep, searchParamsObj, diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index 475702db0c..2a0abd31c7 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -80,14 +80,21 @@ export const buildAzureSecret = (formData: FormData) => { export const buildM365Secret = (formData: FormData) => { const secret = { - ...buildAzureSecret(formData), - [ProviderCredentialFields.USER]: getFormValue( + [ProviderCredentialFields.CLIENT_ID]: getFormValue( formData, - ProviderCredentialFields.USER, + ProviderCredentialFields.CLIENT_ID, ), - [ProviderCredentialFields.PASSWORD]: getFormValue( + [ProviderCredentialFields.TENANT_ID]: getFormValue( formData, - ProviderCredentialFields.PASSWORD, + ProviderCredentialFields.TENANT_ID, + ), + [ProviderCredentialFields.CLIENT_SECRET]: getFormValue( + formData, + ProviderCredentialFields.CLIENT_SECRET, + ), + [ProviderCredentialFields.CERTIFICATE_CONTENT]: getFormValue( + formData, + ProviderCredentialFields.CERTIFICATE_CONTENT, ), }; return filterEmptyValues(secret); diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts index 32dc184292..635c74bf44 100644 --- a/ui/lib/provider-credentials/provider-credential-fields.ts +++ b/ui/lib/provider-credentials/provider-credential-fields.ts @@ -29,6 +29,7 @@ export const ProviderCredentialFields = { TENANT_ID: "tenant_id", USER: "user", PASSWORD: "password", + CERTIFICATE_CONTENT: "certificate_content", // GCP fields REFRESH_TOKEN: "refresh_token", @@ -70,6 +71,7 @@ export const ErrorPointers = { OAUTH_APP_TOKEN: "/data/attributes/secret/oauth_app_token", GITHUB_APP_ID: "/data/attributes/secret/github_app_id", GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content", + CERTIFICATE_CONTENT: "/data/attributes/secret/certificate_content", } as const; export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers]; diff --git a/ui/lib/provider-helpers.ts b/ui/lib/provider-helpers.ts index becd9bc4f2..1bcf5d80bb 100644 --- a/ui/lib/provider-helpers.ts +++ b/ui/lib/provider-helpers.ts @@ -53,7 +53,7 @@ export const getProviderFormType = ( via?: string, ): ProviderFormType => { // Providers that need credential type selection - const needsSelector = ["aws", "gcp", "github"].includes(providerType); + const needsSelector = ["aws", "gcp", "github", "m365"].includes(providerType); // Show selector if no via parameter and provider needs it if (needsSelector && !via) { @@ -80,6 +80,14 @@ export const getProviderFormType = ( return "credentials"; } + // M365 credential types + if ( + providerType === "m365" && + ["app_client_secret", "app_certificate"].includes(via || "") + ) { + return "credentials"; + } + // Other providers go directly to credentials form if (!needsSelector) { return "credentials"; @@ -99,6 +107,8 @@ export const requiresBackButton = (via?: string | null): boolean => { "personal_access_token", "oauth_app", "github_app", + "app_client_secret", + "app_certificate", ]; return validViaTypes.includes(via); diff --git a/ui/types/components.ts b/ui/types/components.ts index 27de816762..6485f7d0db 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -213,15 +213,24 @@ export type AzureCredentials = { [ProviderCredentialFields.PROVIDER_ID]: string; }; -export type M365Credentials = { +export type M365ClientSecretCredentials = { [ProviderCredentialFields.CLIENT_ID]: string; [ProviderCredentialFields.CLIENT_SECRET]: string; [ProviderCredentialFields.TENANT_ID]: string; - [ProviderCredentialFields.USER]?: string; - [ProviderCredentialFields.PASSWORD]?: string; [ProviderCredentialFields.PROVIDER_ID]: string; }; +export type M365CertificateCredentials = { + [ProviderCredentialFields.CLIENT_ID]: string; + [ProviderCredentialFields.CERTIFICATE_CONTENT]: string; + [ProviderCredentialFields.TENANT_ID]: string; + [ProviderCredentialFields.PROVIDER_ID]: string; +}; + +export type M365Credentials = + | M365ClientSecretCredentials + | M365CertificateCredentials; + export type GCPDefaultCredentials = { client_id: string; client_secret: string; diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 10900a3f26..d6421ee16d 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -168,12 +168,13 @@ export const addCredentialsFormSchema = ( .min(1, "Client ID is required"), [ProviderCredentialFields.CLIENT_SECRET]: z .string() - .min(1, "Client Secret is required"), + .optional(), + [ProviderCredentialFields.CERTIFICATE_CONTENT]: z + .string() + .optional(), [ProviderCredentialFields.TENANT_ID]: z .string() .min(1, "Tenant ID is required"), - [ProviderCredentialFields.USER]: z.string().optional(), - [ProviderCredentialFields.PASSWORD]: z.string().optional(), } : providerType === "github" ? { @@ -194,23 +195,26 @@ export const addCredentialsFormSchema = ( }) .superRefine((data: Record, ctx) => { if (providerType === "m365") { - const hasUser = !!data[ProviderCredentialFields.USER]; - const hasPassword = !!data[ProviderCredentialFields.PASSWORD]; - - if (hasUser && !hasPassword) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "If you provide a user, you must also provide a password", - path: [ProviderCredentialFields.PASSWORD], - }); - } - - if (hasPassword && !hasUser) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "If you provide a password, you must also provide a user", - path: [ProviderCredentialFields.USER], - }); + // Validate based on the via parameter + if (via === "app_client_secret") { + const clientSecret = data[ProviderCredentialFields.CLIENT_SECRET]; + if (!clientSecret || clientSecret.trim() === "") { + ctx.addIssue({ + code: "custom", + message: "Client Secret is required", + path: [ProviderCredentialFields.CLIENT_SECRET], + }); + } + } else if (via === "app_certificate") { + const certificateContent = + data[ProviderCredentialFields.CERTIFICATE_CONTENT]; + if (!certificateContent || certificateContent.trim() === "") { + ctx.addIssue({ + code: "custom", + message: "Certificate Content is required", + path: [ProviderCredentialFields.CERTIFICATE_CONTENT], + }); + } } } @@ -219,7 +223,7 @@ export const addCredentialsFormSchema = ( if (via === "personal_access_token") { if (!data[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "Personal Access Token is required", path: [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN], }); @@ -227,7 +231,7 @@ export const addCredentialsFormSchema = ( } else if (via === "oauth_app") { if (!data[ProviderCredentialFields.OAUTH_APP_TOKEN]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "OAuth App Token is required", path: [ProviderCredentialFields.OAUTH_APP_TOKEN], }); @@ -235,14 +239,14 @@ export const addCredentialsFormSchema = ( } else if (via === "github_app") { if (!data[ProviderCredentialFields.GITHUB_APP_ID]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "GitHub App ID is required", path: [ProviderCredentialFields.GITHUB_APP_ID], }); } if (!data[ProviderCredentialFields.GITHUB_APP_KEY]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "GitHub App Private Key is required", path: [ProviderCredentialFields.GITHUB_APP_KEY], }); @@ -390,7 +394,7 @@ export const mutedFindingsConfigFormSchema = z.object({ const yamlValidation = validateYaml(val); if (!yamlValidation.isValid) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: `Invalid YAML format: ${yamlValidation.error}`, }); return; @@ -399,7 +403,7 @@ export const mutedFindingsConfigFormSchema = z.object({ const mutelistValidation = validateMutelistYaml(val); if (!mutelistValidation.isValid) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: `Invalid mutelist structure: ${mutelistValidation.error}`, }); } From d8908d2ccc628f39818af93a65e2ab0dbca6d5cd Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Mon, 20 Oct 2025 14:39:03 +0200 Subject: [PATCH 10/57] docs(fix): space in providers table (#8938) --- docs/introduction.mdx | 66 +++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/docs/introduction.mdx b/docs/introduction.mdx index db37bea7ba..811dc794b7 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -5,69 +5,47 @@ ![](/images/products/overview.png) - + Command Line Interface - + Web Application - + A managed service built on top of Prowler App. - + A public library of versioned checks, cloud service artifacts, and compliance frameworks. ## Supported Providers + The supported providers right now are: -| Provider | Support | Interface | -|----------|--------|----------| -| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | UI, API, CLI | -| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | UI, API, CLI | -| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | UI, API, CLI | -| [Kubernetes](/user-guide/providers/kubernetes/in-cluster) | Official | UI, API, CLI | -| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | UI, API, CLI | -| [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI | -| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | CLI | -| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | CLI | -| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI | -| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI | -| **NHN** | Unofficial | CLI | +| Provider | Support | Interface | +| -------------------------------------------------------------------------------- | ---------- | ------------ | +| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | UI, API, CLI | +| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | UI, API, CLI | +| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | UI, API, CLI | +| [Kubernetes](/user-guide/providers/kubernetes/in-cluster) | Official | UI, API, CLI | +| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | UI, API, CLI | +| [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI | +| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | CLI | +| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | CLI | +| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI | +| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI | +| **NHN** | Unofficial | CLI | For more information about the checks and compliance of each provider visit [Prowler Hub](https://hub.prowler.com). ## Where to go next? + - + Detailed instructions on how to use Prowler. - + Interested in contributing to Prowler? - + \ No newline at end of file From 8e3d50c807355f337091c7226ef97891277b3e89 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Mon, 20 Oct 2025 14:51:15 +0200 Subject: [PATCH 11/57] fix(docs): redirect user-guide-tutorials (#8945) --- docs/docs.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs.json b/docs/docs.json index e72c276586..ca09740603 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -431,6 +431,10 @@ { "source": "/projects/prowler-saas/en/latest/:slug*", "destination": "https://docs.prowler.pro/en/latest/:slug*" + }, + { + "source": "/projects/prowler-open-source/en/latest/tutorials/:slug*", + "destination": "/user-guide/tutorials/:slug*" } ] } From ce7510db28b47847054c434260c46102d4cd7e01 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:58:53 +0200 Subject: [PATCH 12/57] docs: remove anchors from redirects (#8953) --- docs/docs.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index ca09740603..fb7e91a774 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -404,14 +404,6 @@ "source": "/projects/prowler-open-source/en/latest/tutorials/gcp/getting-started-gcp", "destination": "/user-guide/providers/gcp/getting-started-gcp" }, - { - "source": "/projects/prowler-open-source/en/latest/tutorials/prowler-app", - "destination": "/user-guide/tutorials/prowler-app#step-4-4%3A-kubernetes-credentials%3A" - }, - { - "source": "/projects/prowler-open-source/en/latest/tutorials/prowler-app/#step-3-add-a-provider", - "destination": "/user-guide/tutorials/prowler-app#step-3-add-a-provider" - }, { "source": "/projects/prowler-open-source/en/latest/tutorials/microsoft365/getting-started-m365", "destination": "/user-guide/providers/microsoft365/getting-started-m365" From 0fa9e2da6cf8b6e3164f1e64fc86fd95d5e38167 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Mon, 20 Oct 2025 15:20:29 +0200 Subject: [PATCH 13/57] chore(regions_update): Changes in regions for AWS services (#8946) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- .../providers/aws/aws_regions_by_service.json | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index 1c5ea06b5f..f8e2a9f605 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -819,18 +819,6 @@ "aws-us-gov": [] } }, - "apptest": { - "regions": { - "aws": [ - "ap-southeast-2", - "eu-central-1", - "sa-east-1", - "us-east-1" - ], - "aws-cn": [], - "aws-us-gov": [] - } - }, "aps": { "regions": { "aws": [ @@ -8723,6 +8711,7 @@ "ap-southeast-5", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", "eu-south-2", "eu-west-1", @@ -9207,11 +9196,13 @@ "ap-east-2", "ap-northeast-1", "ap-northeast-2", + "ap-northeast-3", "ap-south-1", "ap-south-2", "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", + "ap-southeast-5", "ap-southeast-7", "ca-central-1", "eu-central-1", @@ -12436,7 +12427,12 @@ "workspaces-instances": { "regions": { "aws": [ - "ap-northeast-2" + "ap-east-1", + "ap-northeast-2", + "ap-southeast-5", + "eu-south-2", + "me-central-1", + "us-east-2" ], "aws-cn": [], "aws-us-gov": [] From 5e85ef5835cae6ef2e6a9082ba68746232409635 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:49:09 +0200 Subject: [PATCH 14/57] feat(ui): new card components and derivates for overview (#8921) Co-authored-by: Alan Buscaglia --- ui/components.json | 21 + ui/components/shadcn/README.md | 57 +++ ui/components/shadcn/card.tsx | 92 +++++ ui/components/shadcn/index.ts | 21 + .../shadcn/resource-stats-card/index.ts | 13 + .../resource-stats-card-container.tsx | 55 +++ .../resource-stats-card-content.tsx | 204 +++++++++ .../resource-stats-card-divider.tsx | 59 +++ .../resource-stats-card-header.tsx | 103 +++++ .../resource-stats-card.tsx | 164 ++++++++ ui/dependency-log.json | 8 + ui/package-lock.json | 388 +++++++++++++++++- ui/package.json | 3 +- 13 files changed, 1181 insertions(+), 7 deletions(-) create mode 100644 ui/components.json create mode 100644 ui/components/shadcn/README.md create mode 100644 ui/components/shadcn/card.tsx create mode 100644 ui/components/shadcn/index.ts create mode 100644 ui/components/shadcn/resource-stats-card/index.ts create mode 100644 ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx create mode 100644 ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx create mode 100644 ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx create mode 100644 ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx create mode 100644 ui/components/shadcn/resource-stats-card/resource-stats-card.tsx diff --git a/ui/components.json b/ui/components.json new file mode 100644 index 0000000000..f8da89d768 --- /dev/null +++ b/ui/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "styles/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/shadcn", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/ui/components/shadcn/README.md b/ui/components/shadcn/README.md new file mode 100644 index 0000000000..1bd28c8883 --- /dev/null +++ b/ui/components/shadcn/README.md @@ -0,0 +1,57 @@ +# shadcn Components + +This directory contains all shadcn/ui based components for the Prowler application. + +## Directory Structure + +``` +shadcn/ +├── card.tsx # shadcn Card component +├── resource-stats-card/ # Custom ResourceStatsCard built on shadcn +│ ├── resource-stats-card.tsx +│ ├── resource-stats-card.example.tsx +│ └── index.ts +├── index.ts # Barrel exports +└── README.md +``` + +## Usage + +All shadcn components can be imported from `@/components/shadcn`: + +```tsx +import { Card, CardHeader, CardContent } from "@/components/shadcn"; +import { ResourceStatsCard } from "@/components/shadcn"; +``` + +## Adding New shadcn Components + +When adding new shadcn components using the CLI: + +```bash +npx shadcn@latest add [component-name] +``` + +The component will be automatically added to this directory due to the configuration in `components.json`: + +```json +{ + "aliases": { + "ui": "@/components/shadcn" + } +} +``` + +## Component Guidelines + +1. **shadcn base components** - Use as-is from shadcn/ui (e.g., `card.tsx`) +2. **Custom components built on shadcn** - Create in subdirectories (e.g., `resource-stats-card/`) +3. **CVA variants** - Use Class Variance Authority for type-safe variants +4. **Theme support** - Include `dark:` classes for dark/light theme compatibility +5. **TypeScript** - Always export types and use proper typing + +## Resources + +- [shadcn/ui Documentation](https://ui.shadcn.com) +- [CVA Documentation](https://cva.style/docs) +- [Tailwind CSS Documentation](https://tailwindcss.com/docs) diff --git a/ui/components/shadcn/card.tsx b/ui/components/shadcn/card.tsx new file mode 100644 index 0000000000..a1b4a7742d --- /dev/null +++ b/ui/components/shadcn/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +}; diff --git a/ui/components/shadcn/index.ts b/ui/components/shadcn/index.ts new file mode 100644 index 0000000000..4291e07d5a --- /dev/null +++ b/ui/components/shadcn/index.ts @@ -0,0 +1,21 @@ +export { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "./card"; +export { + ResourceStatsCard, + ResourceStatsCardContainer, + type ResourceStatsCardContainerProps, + ResourceStatsCardContent, + type ResourceStatsCardContentProps, + ResourceStatsCardDivider, + type ResourceStatsCardDividerProps, + ResourceStatsCardHeader, + type ResourceStatsCardHeaderProps, + type ResourceStatsCardProps, + type StatItem, +} from "./resource-stats-card"; diff --git a/ui/components/shadcn/resource-stats-card/index.ts b/ui/components/shadcn/resource-stats-card/index.ts new file mode 100644 index 0000000000..c049987ae0 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/index.ts @@ -0,0 +1,13 @@ +export type { ResourceStatsCardProps } from "./resource-stats-card"; +export { ResourceStatsCard } from "./resource-stats-card"; +export type { ResourceStatsCardContainerProps } from "./resource-stats-card-container"; +export { ResourceStatsCardContainer } from "./resource-stats-card-container"; +export type { + ResourceStatsCardContentProps, + StatItem, +} from "./resource-stats-card-content"; +export { ResourceStatsCardContent } from "./resource-stats-card-content"; +export type { ResourceStatsCardDividerProps } from "./resource-stats-card-divider"; +export { ResourceStatsCardDivider } from "./resource-stats-card-divider"; +export type { ResourceStatsCardHeaderProps } from "./resource-stats-card-header"; +export { ResourceStatsCardHeader } from "./resource-stats-card-header"; diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx b/ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx new file mode 100644 index 0000000000..78dc88fb71 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx @@ -0,0 +1,55 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const containerVariants = cva( + [ + "flex", + "rounded-[12px]", + "border", + "backdrop-blur-[46px]", + "border-[rgba(38,38,38,0.70)]", + "bg-[rgba(23,23,23,0.50)]", + "dark:border-[rgba(38,38,38,0.70)]", + "dark:bg-[rgba(23,23,23,0.50)]", + ], + { + variants: { + padding: { + sm: "px-3 py-2", + md: "px-[19px] py-[9px]", + lg: "px-6 py-3", + none: "p-0", + }, + }, + defaultVariants: { + padding: "md", + }, + }, +); + +export interface ResourceStatsCardContainerProps + extends React.HTMLAttributes, + VariantProps { + ref?: React.Ref; +} + +export const ResourceStatsCardContainer = ({ + className, + children, + padding, + ref, + ...props +}: ResourceStatsCardContainerProps) => { + return ( +
+ {children} +
+ ); +}; + +ResourceStatsCardContainer.displayName = "ResourceStatsCardContainer"; diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx b/ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx new file mode 100644 index 0000000000..d165eb7944 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx @@ -0,0 +1,204 @@ +import { cva } from "class-variance-authority"; +import { LucideIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +export interface StatItem { + icon: LucideIcon; + label: string; +} + +export const CardVariant = { + default: "default", + fail: "fail", + pass: "pass", + warning: "warning", + info: "info", +} as const; + +export type CardVariant = (typeof CardVariant)[keyof typeof CardVariant]; + +const variantColors = { + default: "#868994", + fail: "#f54280", + pass: "#4ade80", + warning: "#fbbf24", + info: "#60a5fa", +} as const; + +type BadgeVariant = keyof typeof variantColors; + +const badgeVariants = cva( + ["flex", "items-center", "justify-center", "gap-0.5", "rounded-full"], + { + variants: { + variant: { + [CardVariant.default]: "bg-[#535359]", + [CardVariant.fail]: "bg-[#432232]", + [CardVariant.pass]: "bg-[#204237]", + [CardVariant.warning]: "bg-[#3d3520]", + [CardVariant.info]: "bg-[#1e3a5f]", + }, + size: { + sm: "px-1 text-xs", + md: "px-1.5 text-sm", + lg: "px-2 text-base", + }, + }, + defaultVariants: { + variant: CardVariant.fail, + size: "md", + }, + }, +); + +const badgeIconVariants = cva("", { + variants: { + size: { + sm: "h-2.5 w-2.5", + md: "h-3 w-3", + lg: "h-4 w-4", + }, + }, + defaultVariants: { + size: "md", + }, +}); + +const labelTextVariants = cva( + "leading-6 font-semibold text-zinc-300 dark:text-zinc-300", + { + variants: { + size: { + sm: "text-xs", + md: "text-sm", + lg: "text-base", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +const statIconVariants = cva("text-zinc-300 dark:text-zinc-300", { + variants: { + size: { + sm: "h-2.5 w-2.5", + md: "h-3 w-3", + lg: "h-3.5 w-3.5", + }, + }, + defaultVariants: { + size: "md", + }, +}); + +const statLabelVariants = cva( + "leading-5 font-medium text-zinc-300 dark:text-zinc-300", + { + variants: { + size: { + sm: "text-xs", + md: "text-sm", + lg: "text-base", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +export interface ResourceStatsCardContentProps + extends React.HTMLAttributes { + badge: { + icon: LucideIcon; + count: number | string; + variant?: CardVariant; + }; + label: string; + stats?: StatItem[]; + accentColor?: string; + size?: "sm" | "md" | "lg"; + ref?: React.Ref; +} + +export const ResourceStatsCardContent = ({ + badge, + label, + stats = [], + accentColor, + size = "md", + className, + ref, + ...props +}: ResourceStatsCardContentProps) => { + const BadgeIcon = badge.icon; + const badgeVariant: BadgeVariant = badge.variant || "fail"; + + // Determine accent line color + const lineColor = accentColor || variantColors[badgeVariant] || "#d4d4d8"; + + return ( +
+ {/* Badge and Label Row */} +
+ {/* Badge */} +
+ + + {badge.count} + +
+ + {/* Label */} + {label} +
+ + {/* Stats Section */} + {stats.length > 0 && ( +
+ {/* Vertical Accent Line */} +
+
+
+ + {/* Stats List */} +
+ {stats.map((stat, index) => { + const StatIcon = stat.icon; + return ( +
+ + + {stat.label} + +
+ ); + })} +
+
+ )} +
+ ); +}; + +ResourceStatsCardContent.displayName = "ResourceStatsCardContent"; diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx b/ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx new file mode 100644 index 0000000000..3ec8d57ee4 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx @@ -0,0 +1,59 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const dividerVariants = cva("flex items-center justify-center", { + variants: { + spacing: { + sm: "px-2", + md: "px-[23px]", + lg: "px-8", + }, + orientation: { + vertical: "h-full", + horizontal: "w-full", + }, + }, + defaultVariants: { + spacing: "md", + orientation: "vertical", + }, +}); + +const lineVariants = cva("bg-[rgba(39,39,42,1)]", { + variants: { + orientation: { + vertical: "h-full w-px", + horizontal: "w-full h-px", + }, + }, + defaultVariants: { + orientation: "vertical", + }, +}); + +export interface ResourceStatsCardDividerProps + extends React.HTMLAttributes, + VariantProps { + ref?: React.Ref; +} + +export const ResourceStatsCardDivider = ({ + className, + spacing, + orientation, + ref, + ...props +}: ResourceStatsCardDividerProps) => { + return ( +
+
+
+ ); +}; + +ResourceStatsCardDivider.displayName = "ResourceStatsCardDivider"; diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx b/ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx new file mode 100644 index 0000000000..2a4068e998 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx @@ -0,0 +1,103 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { LucideIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const headerVariants = cva("flex w-full items-center gap-1", { + variants: { + size: { + sm: "", + md: "", + lg: "", + }, + }, + defaultVariants: { + size: "md", + }, +}); + +const iconVariants = cva("text-zinc-300 dark:text-zinc-300", { + variants: { + size: { + sm: "h-3.5 w-3.5", + md: "h-4 w-4", + lg: "h-5 w-5", + }, + }, + defaultVariants: { + size: "md", + }, +}); + +const titleVariants = cva( + "leading-7 font-semibold text-zinc-300 dark:text-zinc-300", + { + variants: { + size: { + sm: "text-sm", + md: "text-base", + lg: "text-lg", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +const countVariants = cva( + "leading-4 font-normal text-zinc-300 dark:text-zinc-300", + { + variants: { + size: { + sm: "text-[9px]", + md: "text-[10px]", + lg: "text-xs", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +export interface ResourceStatsCardHeaderProps + extends React.HTMLAttributes, + VariantProps { + icon: LucideIcon; + title: string; + resourceCount?: number | string; + ref?: React.Ref; +} + +export const ResourceStatsCardHeader = ({ + icon: Icon, + title, + resourceCount, + size = "md", + className, + ref, + ...props +}: ResourceStatsCardHeaderProps) => { + return ( +
+
+ + {title} +
+ {resourceCount !== undefined && ( + + {typeof resourceCount === "number" + ? `${resourceCount} Resources` + : resourceCount} + + )} +
+ ); +}; + +ResourceStatsCardHeader.displayName = "ResourceStatsCardHeader"; diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card.tsx b/ui/components/shadcn/resource-stats-card/resource-stats-card.tsx new file mode 100644 index 0000000000..7acbd103e3 --- /dev/null +++ b/ui/components/shadcn/resource-stats-card/resource-stats-card.tsx @@ -0,0 +1,164 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { LucideIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +import { ResourceStatsCardContainer } from "./resource-stats-card-container"; +import type { StatItem } from "./resource-stats-card-content"; +import { + CardVariant, + ResourceStatsCardContent, +} from "./resource-stats-card-content"; +import { ResourceStatsCardHeader } from "./resource-stats-card-header"; + +export type { StatItem }; + +// Todo: when the design system is ready, we must use the colors from the design system (semantic colors) +// Variant styles using CVA for type safety and consistency +// Colors are exact HEX values from Figma design system +const cardVariants = cva("", { + variants: { + variant: { + [CardVariant.default]: "", + // Fail variant - rgba(67,34,50) from Figma + [CardVariant.fail]: + "border-[rgba(67,34,50,0.5)] bg-[rgba(67,34,50,0.2)] dark:border-[rgba(67,34,50,0.7)] dark:bg-[rgba(67,34,50,0.3)]", + // Pass variant - rgba(32,66,55) from Figma + [CardVariant.pass]: + "border-[rgba(32,66,55,0.5)] bg-[rgba(32,66,55,0.2)] dark:border-[rgba(32,66,55,0.7)] dark:bg-[rgba(32,66,55,0.3)]", + // Warning variant - rgba(61,53,32) from Figma + [CardVariant.warning]: + "border-[rgba(61,53,32,0.5)] bg-[rgba(61,53,32,0.2)] dark:border-[rgba(61,53,32,0.7)] dark:bg-[rgba(61,53,32,0.3)]", + // Info variant - rgba(30,58,95) from Figma + [CardVariant.info]: + "border-[rgba(30,58,95,0.5)] bg-[rgba(30,58,95,0.2)] dark:border-[rgba(30,58,95,0.7)] dark:bg-[rgba(30,58,95,0.3)]", + }, + size: { + sm: "px-2 py-1.5 gap-1", + md: "px-3 py-2 gap-2", + lg: "px-4 py-3 gap-3", + }, + }, + defaultVariants: { + variant: CardVariant.default, + size: "md", + }, +}); + +export interface ResourceStatsCardProps + extends Omit, "color">, + VariantProps { + // Optional header (icon + title + resource count) + header?: { + icon: LucideIcon; + title: string; + resourceCount?: number | string; + }; + + // Empty state message (when there's no data to display) + emptyState?: { + message: string; + }; + + // Main badge (top section) - optional when using empty state + badge?: { + icon: LucideIcon; + count: number | string; + variant?: CardVariant; + }; + + // Main label - optional when using empty state + label?: string; + + // Vertical accent line color (optional, auto-determined from variant) + accentColor?: string; + + // Sub-statistics array (flexible items) + stats?: StatItem[]; + + // Render without container (no border, background, padding) - useful for composing multiple cards in a custom container + containerless?: boolean; + + // Ref for the root element + ref?: React.Ref; +} + +export const ResourceStatsCard = ({ + header, + emptyState, + badge, + label, + accentColor, + stats = [], + variant = CardVariant.default, + size = "md", + containerless = false, + className, + ref, + ...props +}: ResourceStatsCardProps) => { + // Resolve size to ensure it's not null (CVA can return null but we need a defined value) + const resolvedSize = size || "md"; + + // If containerless, render without outer wrapper + if (containerless) { + return ( +
+ {header && } + {emptyState ? ( +
+

+ {emptyState.message} +

+
+ ) : ( + badge && + label && ( + + ) + )} +
+ ); + } + + // Otherwise, render with container + return ( + + {header && } + {emptyState ? ( +
+

+ {emptyState.message} +

+
+ ) : ( + badge && + label && ( + + ) + )} +
+ ); +}; + +ResourceStatsCard.displayName = "ResourceStatsCard"; diff --git a/ui/dependency-log.json b/ui/dependency-log.json index 826a3075c2..64a12b5901 100644 --- a/ui/dependency-log.json +++ b/ui/dependency-log.json @@ -399,6 +399,14 @@ "strategy": "installed", "generatedAt": "2025-09-10T11:50:17.548Z" }, + { + "section": "dependencies", + "name": "tw-animate-css", + "from": "1.4.0", + "to": "1.4.0", + "strategy": "installed", + "generatedAt": "2025-10-15T07:57:13.225Z" + }, { "section": "dependencies", "name": "uuid", diff --git a/ui/package-lock.json b/ui/package-lock.json index bd629b22fb..b1b02c43fb 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -55,10 +55,10 @@ "recharts": "2.15.4", "rss-parser": "3.13.0", "server-only": "0.0.1", - "shadcn": "3.2.1", "sharp": "0.33.5", "tailwind-merge": "3.3.1", "tailwindcss-animate": "1.0.7", + "tw-animate-css": "1.4.0", "uuid": "11.1.0", "zod": "4.1.11", "zustand": "5.0.8" @@ -91,6 +91,7 @@ "postcss": "8.4.38", "prettier": "3.6.2", "prettier-plugin-tailwindcss": "0.6.14", + "shadcn": "3.4.1", "tailwind-variants": "0.1.20", "tailwindcss": "4.1.13", "typescript": "5.5.4" @@ -193,6 +194,7 @@ "version": "25.0.0", "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-25.0.0.tgz", "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", + "dev": true, "license": "MIT", "dependencies": { "ansis": "^4.0.0", @@ -252,6 +254,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -266,6 +269,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -275,6 +279,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -305,6 +310,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -314,6 +320,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", @@ -330,6 +337,7 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -342,6 +350,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -358,6 +367,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -367,6 +377,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -388,6 +399,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -397,6 +409,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -406,6 +419,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -419,6 +433,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -432,6 +447,7 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -449,6 +465,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -461,6 +478,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -470,6 +488,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", @@ -487,6 +506,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -500,6 +520,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -509,6 +530,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -518,6 +540,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -527,6 +550,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -540,6 +564,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -551,10 +576,27 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -566,10 +608,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -585,6 +645,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -598,6 +678,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -612,6 +693,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -630,6 +712,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -643,6 +726,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, "license": "ISC", "dependencies": { "cookie": "^0.7.2" @@ -652,6 +736,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, "license": "ISC", "dependencies": { "statuses": "^2.0.1" @@ -667,6 +752,7 @@ "version": "1.51.0", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.0.tgz", "integrity": "sha512-CbMGzyOYSyFF7d4uaeYwO9gpSBzLTnMmSmTVpCZjvpJFV69qYbjYPpzNnCz1mb2wIvEhjWjRwQWuBzTO0jITww==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "commander": "^11.1.0", @@ -690,6 +776,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16" @@ -699,6 +786,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -722,6 +810,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -739,6 +828,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -751,6 +841,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -760,6 +851,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -772,6 +864,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=16" @@ -781,6 +874,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -790,6 +884,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -802,6 +897,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -817,6 +913,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -829,12 +926,14 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -844,6 +943,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" @@ -859,6 +959,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.4.tgz", "integrity": "sha512-t+iX+Wf5nRKyNzk8dviW3Ikb/280+aEJAnw9YXvCp2tYGPSkMki+NRY+8aNLmVFv3eNtMdvViPNOPxS8SZNP+w==", + "dev": true, "license": "MIT", "engines": { "bun": ">=1", @@ -3278,6 +3379,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.0.tgz", "integrity": "sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3287,6 +3389,7 @@ "version": "5.1.18", "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.18.tgz", "integrity": "sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==", + "dev": true, "license": "MIT", "dependencies": { "@inquirer/core": "^10.2.2", @@ -3308,6 +3411,7 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", + "dev": true, "license": "MIT", "dependencies": { "@inquirer/ansi": "^1.0.0", @@ -3335,6 +3439,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3350,12 +3455,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/@inquirer/core/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3365,6 +3472,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3379,6 +3487,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -3393,6 +3502,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3402,6 +3512,7 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3456,6 +3567,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, "license": "MIT", "engines": { "node": "20 || >=22" @@ -3465,6 +3577,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -3789,6 +3902,7 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.1.tgz", "integrity": "sha512-d//GE8/Yh7aC3e7p+kZG8JqqEAwwDUmAfvH1quogtbk+ksS6E0RR6toKKESPYYZVre0meqkJb27zb+dhqE9Sgw==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.6", @@ -3812,6 +3926,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -3821,6 +3936,7 @@ "version": "3.24.6", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, "license": "ISC", "peerDependencies": { "zod": "^3.24.1" @@ -3830,6 +3946,7 @@ "version": "0.39.7", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.7.tgz", "integrity": "sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==", + "dev": true, "license": "MIT", "dependencies": { "@open-draft/deferred-promise": "^2.2.0", @@ -4016,6 +4133,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -4028,6 +4146,7 @@ "version": "1.9.7", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -4043,6 +4162,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -4055,6 +4175,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4068,6 +4189,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4077,6 +4199,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4100,12 +4223,14 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, "license": "MIT" }, "node_modules/@open-draft/logger": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, "license": "MIT", "dependencies": { "is-node-process": "^1.2.0", @@ -4116,6 +4241,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, "license": "MIT" }, "node_modules/@opentelemetry/api": { @@ -7161,12 +7287,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7564,6 +7692,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, "license": "MIT", "dependencies": { "fast-glob": "^3.3.3", @@ -7575,6 +7704,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7591,6 +7721,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -7603,6 +7734,7 @@ "version": "10.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, "license": "ISC", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" @@ -7628,6 +7760,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-array": { @@ -7764,7 +7897,7 @@ "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/react": { @@ -7796,6 +7929,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, "license": "MIT" }, "node_modules/@types/unist": { @@ -8282,6 +8416,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -8318,6 +8453,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -8345,6 +8481,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -8387,6 +8524,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8408,6 +8546,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -8615,6 +8754,7 @@ "version": "0.16.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.1" @@ -8765,6 +8905,7 @@ "version": "2.8.6", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -8774,6 +8915,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -8804,6 +8946,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8816,6 +8959,7 @@ "version": "4.26.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -8849,6 +8993,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -8877,6 +9022,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8890,6 +9036,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8906,6 +9053,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9049,6 +9197,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" @@ -9064,6 +9213,7 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9093,6 +9243,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, "license": "ISC", "engines": { "node": ">= 12" @@ -9108,6 +9259,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -9122,6 +9274,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -9137,12 +9290,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9152,6 +9307,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9166,6 +9322,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -9192,6 +9349,7 @@ "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, "license": "MIT" }, "node_modules/color": { @@ -9294,6 +9452,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -9306,6 +9465,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9315,12 +9475,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9330,6 +9492,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -9339,6 +9502,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -9352,6 +9516,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -9378,6 +9543,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -9538,6 +9704,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -9662,6 +9829,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -9728,6 +9896,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -9774,6 +9943,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -9819,6 +9989,7 @@ "version": "17.2.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -9831,6 +10002,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -9845,6 +10017,7 @@ "version": "0.4.15", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.15.tgz", "integrity": "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==", + "dev": true, "license": "MIT", "dependencies": { "@ecies/ciphers": "^0.2.3", @@ -9862,12 +10035,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.223", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz", "integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -9881,6 +10056,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -9912,6 +10088,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9934,6 +10111,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -10012,6 +10190,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10021,6 +10200,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10058,6 +10238,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -10117,6 +10298,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10126,6 +10308,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, "license": "MIT" }, "node_modules/escape-string-regexp": { @@ -10862,6 +11045,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -10931,6 +11115,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10947,6 +11132,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -10992,6 +11178,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -11034,6 +11221,7 @@ "version": "7.5.1", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 16" @@ -11055,6 +11243,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -11107,6 +11296,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -11120,6 +11310,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -11129,6 +11320,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, "funding": [ { "type": "github", @@ -11152,6 +11344,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" @@ -11180,6 +11373,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -11192,6 +11386,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -11273,6 +11468,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -11285,6 +11481,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11335,6 +11532,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -11344,6 +11542,7 @@ "version": "11.3.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -11380,6 +11579,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11420,18 +11620,21 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, "license": "MIT" }, "node_modules/fzf": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fzf/-/fzf-0.5.2.tgz", "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -11441,6 +11644,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -11450,6 +11654,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -11462,6 +11667,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -11495,6 +11701,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -11507,6 +11714,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -11677,6 +11885,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11702,6 +11911,7 @@ "version": "16.11.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -11762,6 +11972,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11790,6 +12001,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -11842,6 +12054,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, "license": "MIT" }, "node_modules/html-url-attributes": { @@ -11858,6 +12071,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -11874,6 +12088,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -11883,6 +12098,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -11922,6 +12138,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -11934,6 +12151,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -11943,6 +12161,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -11981,6 +12200,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/inline-style-parser": { @@ -12039,6 +12259,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -12090,6 +12311,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -12233,6 +12455,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12290,6 +12513,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -12312,6 +12536,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12350,12 +12575,14 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, "license": "MIT" }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -12382,6 +12609,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12416,6 +12644,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, "license": "MIT" }, "node_modules/is-regex": { @@ -12441,6 +12670,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -12546,6 +12776,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -12611,6 +12842,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -12680,6 +12912,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -12699,6 +12932,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema": { @@ -12711,6 +12945,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -12724,6 +12959,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -12736,6 +12972,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -12783,6 +13020,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -13115,6 +13353,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/lint-staged": { @@ -13220,6 +13459,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", @@ -13236,6 +13476,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -13248,6 +13489,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -13377,6 +13619,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -13416,6 +13659,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13578,6 +13822,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -13587,6 +13832,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -13599,12 +13845,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -14056,6 +14304,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -14069,6 +14318,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -14078,6 +14328,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -14103,6 +14354,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -14131,6 +14383,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14182,6 +14435,7 @@ "version": "2.11.3", "resolved": "https://registry.npmjs.org/msw/-/msw-2.11.3.tgz", "integrity": "sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -14227,6 +14481,7 @@ "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -14248,6 +14503,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" @@ -14298,6 +14554,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -14839,6 +15096,7 @@ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": "Use your platform's native DOMException instead", + "dev": true, "funding": [ { "type": "github", @@ -14858,6 +15116,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -14876,6 +15135,7 @@ "version": "2.0.21", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, "license": "MIT" }, "node_modules/normalize-range": { @@ -14939,6 +15199,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -14961,6 +15222,7 @@ "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -15060,6 +15322,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -15072,6 +15335,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -15115,6 +15379,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", @@ -15138,6 +15403,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -15150,6 +15416,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -15162,6 +15429,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -15177,6 +15445,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, "license": "MIT" }, "node_modules/own-keys": { @@ -15289,12 +15558,14 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "dev": true, "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -15332,6 +15603,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -15350,6 +15622,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -15362,6 +15635,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -15371,6 +15645,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, "license": "MIT" }, "node_modules/path-exists": { @@ -15397,6 +15672,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15413,6 +15689,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, "license": "MIT" }, "node_modules/path-type": { @@ -15435,6 +15712,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -15460,6 +15738,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -15705,6 +15984,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, "license": "MIT", "dependencies": { "parse-ms": "^4.0.0" @@ -15720,6 +16000,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -15733,6 +16014,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15763,6 +16045,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -15776,6 +16059,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15785,6 +16069,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -15800,6 +16085,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -15897,6 +16183,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -15906,6 +16193,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -15921,6 +16209,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -16124,6 +16413,7 @@ "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, "license": "MIT", "dependencies": { "ast-types": "^0.16.1", @@ -16284,6 +16574,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16314,6 +16605,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -16333,6 +16625,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, "license": "MIT", "dependencies": { "onetime": "^7.0.0", @@ -16349,6 +16642,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" @@ -16373,12 +16667,14 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "dev": true, "license": "MIT" }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -16413,6 +16709,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -16429,6 +16726,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -16449,6 +16747,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -16492,6 +16791,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -16557,6 +16857,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/sax": { @@ -16596,6 +16897,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.5", @@ -16618,6 +16920,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -16688,20 +16991,24 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, "license": "ISC" }, "node_modules/shadcn": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.2.1.tgz", - "integrity": "sha512-j+yLAXa6CFBz96+O6dtiA+BllDOokYH65eofCYbPY2+NBsFYiSKbyfj6psbujNfng9HP7N9/+j6Uzo2iYxRqtw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.4.1.tgz", + "integrity": "sha512-gKLLCGN0lZ9vjbndoGY2cZJKvDtUYGRyF0XoyejHBbCr9lwE6NGlrtRQbA50Wcd/pDdYFyOzT/1wl//HUjSSkw==", + "dev": true, "license": "MIT", "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.17.2", + "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", @@ -16733,6 +17040,7 @@ "version": "14.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -16742,6 +17050,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", @@ -16768,6 +17077,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -16784,6 +17094,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, "license": "MIT", "dependencies": { "@sec-ant/readable-stream": "^0.4.1", @@ -16800,6 +17111,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -16812,6 +17124,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -16821,6 +17134,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -16833,6 +17147,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^4.0.0", @@ -16849,6 +17164,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -16861,6 +17177,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16889,6 +17206,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -16901,6 +17219,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -16915,6 +17234,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -16924,6 +17244,7 @@ "version": "3.24.6", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, "license": "ISC", "peerDependencies": { "zod": "^3.24.1" @@ -16972,6 +17293,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -16984,6 +17306,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16993,6 +17316,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -17012,6 +17336,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -17028,6 +17353,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -17046,6 +17372,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -17065,6 +17392,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -17098,6 +17426,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, "license": "MIT" }, "node_modules/slash": { @@ -17144,6 +17473,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -17179,6 +17509,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -17188,6 +17519,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -17214,6 +17546,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, "license": "MIT" }, "node_modules/string-argv": { @@ -17230,6 +17563,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -17247,6 +17581,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -17259,12 +17594,14 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -17407,6 +17744,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-keys": "^1.0.0", @@ -17424,6 +17762,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -17436,6 +17775,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -17688,6 +18028,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { @@ -17742,6 +18083,7 @@ "version": "7.0.16", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^7.0.16" @@ -17754,12 +18096,14 @@ "version": "7.0.16", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -17772,6 +18116,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.6" @@ -17781,6 +18126,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tldts": "^7.0.5" @@ -17826,6 +18172,7 @@ "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, "license": "MIT", "dependencies": { "@ts-morph/common": "~0.27.0", @@ -17864,6 +18211,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17894,6 +18250,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -17986,7 +18343,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18019,6 +18376,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -18118,6 +18476,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -18127,6 +18486,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -18171,6 +18531,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/kettanaito" @@ -18180,6 +18541,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -18210,6 +18572,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -18335,6 +18698,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -18394,6 +18758,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -18403,6 +18768,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -18577,6 +18943,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/xml2js": { @@ -18605,6 +18972,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -18614,6 +18982,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -18633,6 +19002,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -18651,6 +19021,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -18660,12 +19031,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18675,6 +19048,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -18702,6 +19076,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -18714,6 +19089,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" diff --git a/ui/package.json b/ui/package.json index e0ec108dfc..7dcce36d8c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -69,10 +69,10 @@ "recharts": "2.15.4", "rss-parser": "3.13.0", "server-only": "0.0.1", - "shadcn": "3.2.1", "sharp": "0.33.5", "tailwind-merge": "3.3.1", "tailwindcss-animate": "1.0.7", + "tw-animate-css": "1.4.0", "uuid": "11.1.0", "zod": "4.1.11", "zustand": "5.0.8" @@ -105,6 +105,7 @@ "postcss": "8.4.38", "prettier": "3.6.2", "prettier-plugin-tailwindcss": "0.6.14", + "shadcn": "3.4.1", "tailwind-variants": "0.1.20", "tailwindcss": "4.1.13", "typescript": "5.5.4" From 756d436a2f1e863ba4e302ef3c3be0568dc7540f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Tue, 21 Oct 2025 03:16:05 +0200 Subject: [PATCH 15/57] feat(compliance): improve CCC catalogs (#8944) --- prowler/compliance/aws/ccc_aws.json | 2754 ++++------------------ prowler/compliance/azure/ccc_azure.json | 2789 ++++------------------- prowler/compliance/gcp/ccc_gcp.json | 2766 ++++------------------ 3 files changed, 1182 insertions(+), 7127 deletions(-) diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index ba00424a72..1f6da6d5f6 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -6,19 +6,19 @@ "Description": "Common Cloud Controls Catalog (CCC) for AWS", "Requirements": [ { - "Id": "CCC.AuditLog.C01.TR01", + "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature.\n", + "Recommendation": "Ensure hash of data is included in digital signature. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -49,19 +49,19 @@ ] }, { - "Id": "CCC.AuditLog.C01.TR02", + "Id": "CCC.AuditLog.CN01.AR02", "Description": "When the signature validation process is performed, then it MUST detect any missing (deleted) log file.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function.\n", + "Recommendation": "Ensure verification process includes a chained hash function. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -92,15 +92,15 @@ ] }, { - "Id": "CCC.AuditLog.C02.TR01", - "Description": "When a manual action is performed to generate each audit log type,\nthen the corresponding audit log type MUST be generated and recorded.", + "Id": "CCC.AuditLog.CN02.AR01", + "Description": "When a manual action is performed to generate each audit log type, then the corresponding audit log type MUST be generated and recorded.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C02 Enable And Validate All Audit Log Types", + "Section": "CCC.AuditLog.CN02 Enable And Validate All Audit Log Types", "SubSection": "", - "SubSectionObjective": "Review audit log configuration and ensure that all audit log types\nare being generated and replicated to configured sinks", + "SubSectionObjective": "Review audit log configuration and ensure that all audit log types are being generated and replicated to configured sinks", "Applicability": [ "tlp-red", "tlp-amber" @@ -149,20 +149,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR01", + "Id": "CCC.AuditLog.CN03.AR01", "Description": "When an attempt is made to disable a log source, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -200,20 +200,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR02", - "Description": "When an attempt is made to alter the retention or object lock status\nof an external data log source or bucket, then an alert MUST be generated.", + "Id": "CCC.AuditLog.CN03.AR02", + "Description": "When an attempt is made to alter the retention or object lock status of an external data log source or bucket, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -254,20 +254,20 @@ ] }, { - "Id": "CCC.AuditLog.C04.TR01", - "Description": "When audit log buckets are created then verify that server access\nlogging MUST be enabled for the audit log bucket,\nwith logs delivered to a separate, secure logging bucket.", + "Id": "CCC.AuditLog.CN04.AR01", + "Description": "When audit log buckets are created then verify that server access logging MUST be enabled for the audit log bucket, with logs delivered to a separate, secure logging bucket.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C04 Ensure Access Logging Is Enabled on the Audit Log Bucket", + "Section": "CCC.AuditLog.CN04 Ensure Access Logging Is Enabled on the Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to\ncapture all requests made to the bucket, providing an audit trail of data access.", + "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to capture all requests made to the bucket, providing an audit trail of data access.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging.\nEnsure the target logging bucket is configured for appropriate security,\nincluding restricted access and immutability.\n", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -303,20 +303,20 @@ ] }, { - "Id": "CCC.AuditLog.C05.TR01", + "Id": "CCC.AuditLog.CN05.AR01", "Description": "When audit logs are exported, then audit logs MUST be present in the configured data location.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C05 Export Audit Logs To Bucket", + "Section": "CCC.AuditLog.CN05 Export Audit Logs To Bucket", "SubSection": "", - "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated\nand can be subject to greater access control and data retention polices.", + "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated and can be subject to greater access control and data retention polices.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting.\n", + "Recommendation": "Configure audit log exporting. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -357,21 +357,21 @@ ] }, { - "Id": "CCC.AuditLog.C06.TR01", - "Description": "When the retention policy is applied, then data MUST\nbe automatically deleted after the configured number of days.", + "Id": "CCC.AuditLog.CN06.AR01", + "Description": "When the retention policy is applied, then data MUST be automatically deleted after the configured number of days.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C06 Enforce Retention Policy on Audit Log Bucket", + "Section": "CCC.AuditLog.CN06 Enforce Retention Policy on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are\nretained for the correct number of days as defined by your organization's policy.", + "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are retained for the correct number of days as defined by your organization's policy.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce\nthe required data retention period.\n", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -403,21 +403,21 @@ ] }, { - "Id": "CCC.AuditLog.C07.TR01", - "Description": "When a standard file deletion is attempted on an object within\nthe audit log bucket, then it MUST be prevented unless MFA is provided.", + "Id": "CCC.AuditLog.CN07.AR01", + "Description": "When a standard file deletion is attempted on an object within the audit log bucket, then it MUST be prevented unless MFA is provided.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C07 Enforce MFA Delete on Audit Log Bucket", + "Section": "CCC.AuditLog.CN07 Enforce MFA Delete on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to\nprovide greater protection against accidental or malicious deletion of audit data.", + "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to provide greater protection against accidental or malicious deletion of audit data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations)\non the audit log bucket.\n", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -449,20 +449,20 @@ ] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to delete data before the object\nlock period expires, then the deletion MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to delete data before the object lock period expires, then the deletion MUST be denied.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C08 Enable Object Lock On Audit Log Bucket", + "Section": "CCC.AuditLog.CN08 Enable Object Lock On Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket.\nThe lock time MUST be configured to meet your organization, legal and compliance goals.\nDeletion attempts before the lock period MUST be denied.", + "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket. The lock time MUST be configured to meet your organization, legal and compliance goals. Deletion attempts before the lock period MUST be denied.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -493,20 +493,20 @@ ] }, { - "Id": "CCC.AuditLog.C09.TR01", + "Id": "CCC.AuditLog.CN09.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C09 Restrict Field And Log Type Access", + "Section": "CCC.AuditLog.CN09 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data.\n", + "Recommendation": "Review field level access controls on audit data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -545,21 +545,21 @@ ] }, { - "Id": "CCC.AuditLog.C10.TR01", - "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny\npublic read and write access.", + "Id": "CCC.AuditLog.CN10.AR01", + "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -593,21 +593,21 @@ ] }, { - "Id": "CCC.AuditLog.C10.TR02", - "Description": "When the URL of a audit log storage bucket's object is accessed publicly then,\nit should be denied by bucket policy.", + "Id": "CCC.AuditLog.CN10.AR02", + "Description": "When the URL of a audit log storage bucket's object is accessed publicly then, it should be denied by bucket policy.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -642,15 +642,15 @@ ] }, { - "Id": "CCC.Build.C01.TR01", + "Id": "CCC.Build.CN01.AR01", "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", "Attributes": [ { "FamilyName": "Access Control", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C01 Restrict Allowed Build Agents", + "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", "SubSection": "", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain\ncontrol over the build environment and prevent unauthorized code execution.", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", "Applicability": [ "tlp-red", "tlp-amber" @@ -693,15 +693,15 @@ ] }, { - "Id": "CCC.Build.C02.TR01", - "Description": "Attempt to trigger a build from an unauthorized external service or\nrepository and verify that the build does not start.", + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", "Attributes": [ { "FamilyName": "Access Control", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C02 Restrict Allowed External Services for Build Triggers", + "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", "SubSection": "", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or\nrepositories to prevent unauthorized code execution or tampering.", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", "Applicability": [ "tlp-red", "tlp-amber" @@ -740,15 +740,15 @@ ] }, { - "Id": "CCC.Build.C03.TR01", + "Id": "CCC.Build.CN03.AR01", "Description": "Attempt to access the build environment from an external network and verify that access is denied.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C03 Deny External Network Access for Build Environments", + "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", "SubSection": "", - "SubSectionObjective": "Ensure that build environments do not have external network access to\nprevent unauthorized external access and data exfiltration.", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", "Applicability": [ "tlp-red", "tlp-amber" @@ -785,15 +785,15 @@ ] }, { - "Id": "CCC.CntrReg.C01.TR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry\nand observe if it is flagged or rejected by the vulnerability scanning process.", + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", "Attributes": [ { "FamilyName": "Risk Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C01 Implement Vulnerability Scanning for Artifacts", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for\nvulnerabilities to identify and remediate security issues before deployment.", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", "Applicability": [ "tlp-red", "tlp-amber" @@ -830,59 +830,15 @@ ] }, { - "Id": "CCC.CntrReg.C02.TR01", - "Description": "Confirm that artifacts older than the specified retention period are automatically\ndeleted from the registry.", - "Attributes": [ - { - "FamilyName": "Data Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C02 Implement Cleanup Policies for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to\nmanage storage effectively and reduce security risks associated with outdated versions.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-12" - ] - } - ] - } - ], - "Checks": [ - "ecr_repositories_lifecycle_policy_enabled", - "s3_bucket_lifecycle_enabled" - ] - }, - { - "Id": "CCC.DataWar.C01.TR01", - "Description": "Attempt to access underlying database tables directly without\nusing managed views and verify that access is denied.", + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C01 Enforce Use of Managed Views for Data Access", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users\nfrom accessing underlying tables directly and enforcing consistent security policies.", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", "Applicability": [ "tlp-red", "tlp-amber" @@ -916,15 +872,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C02.TR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and\nverify that access is denied or data is masked.", + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C02 Enforce Column-Level Security Policies", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles,\npreventing unauthorized access to sensitive information.", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", "Applicability": [ "tlp-red", "tlp-amber" @@ -958,15 +914,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C03.TR01", - "Description": "Attempt to query data rows that the user should not have access to and verify\nthat access is denied or data is not returned.", + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C03 Enforce Row-Level Security Policies", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes,\npreventing unauthorized access to specific subsets of data.", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", "Applicability": [ "tlp-red", "tlp-amber" @@ -1000,743 +956,13 @@ "Checks": [] }, { - "Id": "CCC.GenAI.C01.TR01", - "Description": "Untrusted input such as user queries, RAG data or tool output\nMUST be validated before it is passed to a GenAI model.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_model_invocation_logging_enabled", - "bedrock_model_invocation_logs_encryption_enabled", - "bedrock_agent_guardrail_enabled" - ] - }, - { - "Id": "CCC.GenAI.C01.TR02", - "Description": "If malicious patterns such as prompt injection or sensitive\ndata are detected during input validation, the input MUST\nbe blocked or sanitised.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled" - ] - }, - { - "Id": "CCC.GenAI.C02.TR01", - "Description": "GenAI model output MUST be validated for format conformance,\nmalicious patterns, sensitive data and inapropriate content\nbefore being passed to users, application or plugins.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_guardrail_prompt_attack_filter_enabled" - ] - }, - { - "Id": "CCC.GenAI.C02.TR02", - "Description": "In the event of policy violations, the AI-generated content MUST\nbe redacted, encoded or rejected.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_model_invocation_logging_enabled", - "bedrock_model_invocation_logs_encryption_enabled", - "bedrock_agent_guardrail_enabled" - ] - }, - { - "Id": "CCC.GenAI.C03.TR01", - "Description": "When data is designated for model training or RAG ingestion, then its\nsource MUST be explicitly approved and its provenance documented.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C03.TR02", - "Description": "Data from unvetted sources MUST NOT be used in production systems.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [ - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries", - "s3_bucket_cross_account_access", - "s3_bucket_cross_region_replication", - "s3_bucket_public_access", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "s3_bucket_secure_transport_policy", - "s3_bucket_kms_encryption", - "s3_account_level_public_access_blocks", - "s3_bucket_level_public_access_block", - "s3_access_point_public_access_block" - ] - }, - { - "Id": "CCC.GenAI.C04.TR01", - "Description": "When data is ingested for training, fine-tuning or conversion\nto vector embeddings, it MUST be validated for sensitive\ninformation or malicious content.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [ - "cloudformation_stack_outputs_find_secrets", - "ec2_instance_secrets_user_data", - "ec2_launch_template_no_secrets", - "ssm_document_secrets", - "cloudwatch_log_group_no_secrets_in_logs", - "awslambda_function_no_secrets_in_variables", - "awslambda_function_no_secrets_in_code" - ] - }, - { - "Id": "CCC.GenAI.C04.TR02", - "Description": "If sensitive data or malicious content is detected, it must\nbe rejected, redacted or flagged for manual review.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_agent_guardrail_enabled" - ] - }, - { - "Id": "CCC.GenAI.C05.TR01", - "Description": "When a RAG-enabled system generates a response containing information\nretrieved from its knowledge base, then the response MUST include a\nverifiable citation that links back to the specific source document.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C05 Citations and Source Traceability", - "SubSection": "", - "SubSectionObjective": "Require the GenAI system to provide citations or direct links\nback to the source documents used to generate a response, in\nto enhance the transparency, trustworthiness, and verifiability\nof AI-generated content.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH09", - "CCC.GenAI.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-DET-013" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_log_file_validation_enabled" - ] - }, - { - "Id": "CCC.GenAI.C06.TR01", - "Description": "When an LLM invokes an external tool (e.g., an API, a plugin),\nthen the tool MUST operate with the least privileges required\nfor performing its intended functionality.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", - "Section": "CCC.GenAI.C06 Least Privilege for Plugins", - "SubSection": "", - "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system\ncan call to limit the potential damage if an agent is coerced\nto perform unintended actions or vulnerabilities in the tools\nare exploited.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH07", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Agent Permissions" - ] - } - ] - } - ], - "Checks": [ - "apigateway_restapi_authorizers_enabled", - "apigatewayv2_api_authorizers_enabled", - "apigateway_restapi_public", - "apigateway_restapi_public_with_authorizer", - "awslambda_function_url_public", - "awslambda_function_not_publicly_accessible", - "iam_policy_allows_privilege_escalation", - "iam_inline_policy_allows_privilege_escalation", - "iam_policy_no_full_access_to_cloudtrail", - "iam_policy_no_full_access_to_kms", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_group_administrator_access_policy", - "iam_user_administrator_access_policy", - "iam_role_administratoraccess_policy" - ] - }, - { - "Id": "CCC.GenAI.C07.TR01", - "Description": "When an application makes an API call to a foundational model in a\nproduction environment, then it MUST specify an explicit version\nidentifier.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "The Configuration Management control family involves establishing,\nmaintaining and monitoring the configuration of the service and\nrelated applications and infrastructure to ensure consistency,\nsecure defaults and compliance.\n", - "Section": "CCC.GenAI.C07 Model Version Pinning", - "SubSection": "", - "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific,\ntested version of a foundational model to prevent unexpected\nbehaviour changes introduced by provider-side updates.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-010" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C08.TR01", - "Description": "When a new AI model is considered for production deployment, it\nMUST undergo a formal red teaming and quality assurance review.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_agent_guardrail_enabled", - "bedrock_model_invocation_logging_enabled", - "bedrock_model_invocation_logs_encryption_enabled", - "bedrock_api_key_no_administrative_privileges", - "bedrock_api_key_no_long_term_credentials" - ] - }, - { - "Id": "CCC.GenAI.C08.TR02", - "Description": "If model quality review or red teaming identifies an issue that exceeds\nthe organization's risk tolerance, the model MUST NOT be deployed until\nthe issue is remediated.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_model_invocation_logging_enabled", - "bedrock_model_invocation_logs_encryption_enabled", - "bedrock_agent_guardrail_enabled", - "bedrock_api_key_no_administrative_privileges", - "bedrock_api_key_no_long_term_credentials" - ] - }, - { - "Id": "CCC.KeyMgmt.C01.TR01", - "Description": "When a key version is scheduled for deletion or disabled, an\nalert MUST be generated within five minutes.", + "Id": "CCC.KeyMgmt.CN01.AR01", + "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", "Attributes": [ { "FamilyName": "Logging and Metrics Publication", "FamilyDescription": "Controls that collect, alert, and retain key-management events.", - "Section": "CCC.KeyMgmt.C01 Alert on Key-version Changes", + "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", "SubSection": "", "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", "Applicability": [ @@ -1773,13 +999,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C02.TR01", - "Description": "When IAM roles and key policies are reviewed, Decrypt permission\nMUST be granted exclusively to documented authorised principals.", + "Id": "CCC.KeyMgmt.CN02.AR01", + "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", - "Section": "CCC.KeyMgmt.C02 Limit Decrypt Permissions", + "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", "SubSection": "", "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", "Applicability": [ @@ -1819,13 +1045,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C03.TR01", - "Description": "When rotation settings are examined, rotation MUST be enabled with\nan interval not exceeding 365 days.", + "Id": "CCC.KeyMgmt.CN03.AR01", + "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C03 Enforce Automatic Rotation", + "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", "SubSection": "", "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", "Applicability": [ @@ -1861,13 +1087,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C04.TR01", - "Description": "When a key import request is processed, the key MUST use an\napproved algorithm (RSA-2048+, EC-P256+) and originate from a\ncertified HSM.", + "Id": "CCC.KeyMgmt.CN04.AR01", + "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C04 Validate Imported Keys", + "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", "SubSection": "", "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", "Applicability": [ @@ -1906,13 +1132,13 @@ ] }, { - "Id": "CCC.LB.C01.TR01", - "Description": "When a single client sends more than 2000 requests within any\n5-minute sliding window, the load balancer MUST throttle all\nsubsequent requests from that client for at least 60 seconds.", + "Id": "CCC.LB.CN01.AR01", + "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1920,7 +1146,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via\nsynthetic traffic tests.\n", + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1957,13 +1183,13 @@ ] }, { - "Id": "CCC.LB.C01.TR02", - "Description": "When throttling is invoked, the load balancer MUST\nrecord the event in the access log within 5 minutes\nfor alerting and trend analysis.", + "Id": "CCC.LB.CN01.AR02", + "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1971,7 +1197,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Enable access logging and configure metric filters\non HTTP 429 counts to trigger alerts.\n", + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2008,21 +1234,21 @@ ] }, { - "Id": "CCC.LB.C06.TR01", - "Description": "When more than 10 percent of targets change from healthy to\nunhealthy within five minutes, an alert MUST be issued.", + "Id": "CCC.LB.CN06.AR01", + "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C06 Secure Health-Check Telemetry", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", "SubSection": "", - "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on\nabnormal status changes.", + "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Instrument metrics for health check results and target\nremoval events. Configure monitoring alarms to alert\non abnormal spikes in unhealthy targets.\n", + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2056,21 +1282,21 @@ ] }, { - "Id": "CCC.LB.C04.TR01", - "Description": "When routing weights change, the request MUST originate\nfrom an explicitly defined and trusted identity and MUST\nbe logged.", + "Id": "CCC.LB.CN04.AR01", + "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C04 Enforce Distribution Policies", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN04 Enforce Distribution Policies", "SubSection": "", - "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified\nonly by trusted identities.", + "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Define a list of trusted principals allowed to modify\nrouting configurations. Enforce via conditional access\npolicies, and log changes using audit logging.\n", + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2105,15 +1331,15 @@ ] }, { - "Id": "CCC.LB.C05.TR01", - "Description": "When stickiness is enabled, session cookies MUST expire\nwithin 30 minutes of inactivity.", + "Id": "CCC.LB.CN05.AR01", + "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C05 Validate Session Affinity", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN05 Validate Session Affinity", "SubSection": "", - "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking\nrisks.", + "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2162,15 +1388,15 @@ ] }, { - "Id": "CCC.LB.C09.TR01", - "Description": "When an API call originates outside the approved CIDR\nset, the request MUST be denied.", + "Id": "CCC.LB.CN09.AR01", + "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C09 Restrict Management API Access", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN09 Restrict Management API Access", "SubSection": "", - "SubSectionObjective": "Limit load-balancer API calls to authorised identities and\ntrusted networks.", + "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2208,15 +1434,15 @@ ] }, { - "Id": "CCC.LB.C02.TR01", - "Description": "When concurrent connections reach 80 percent of capacity, the\nautoscaling group MUST add at least one instance within five\nminutes.", + "Id": "CCC.LB.CN02.AR01", + "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C02 Auto-Scale Load Balancer Capacity", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", "SubSection": "", - "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic\nspikes.", + "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2256,15 +1482,15 @@ ] }, { - "Id": "CCC.LB.C07.TR01", - "Description": "When responses pass through the load balancer, the\n\"Server\" header MUST be replaced with \"lb\".", + "Id": "CCC.LB.CN07.AR01", + "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C07 Scrub Sensitive Headers", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN07 Scrub Sensitive Headers", "SubSection": "", - "SubSectionObjective": "Remove headers that disclose internal details or software\nversions from HTTP responses.", + "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2298,15 +1524,15 @@ "Checks": [] }, { - "Id": "CCC.LB.C08.TR01", - "Description": "When a certificate is within 30 days of expiry, automated renewal\nMUST complete and deploy a new certificate within 24 hours.", + "Id": "CCC.LB.CN08.AR01", + "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", "Attributes": [ { "FamilyName": "Encryption", "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", - "Section": "CCC.LB.C08 Automate Certificate Renewal", + "Section": "CCC.LB.CN08 Automate Certificate Renewal", "SubSection": "", - "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and\ndeployment before expiry.", + "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2344,15 +1570,15 @@ ] }, { - "Id": "CCC.Logging.C01.TR01", - "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be\nenabled by default and directed to the central sink.", + "Id": "CCC.Logging.CN01.AR01", + "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2402,15 +1628,15 @@ ] }, { - "Id": "CCC.Logging.C01.TR02", - "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant\nlogs (e.g., OS, application, service logs) to the central log sink.", + "Id": "CCC.Logging.CN01.AR02", + "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2459,15 +1685,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR01", - "Description": "When a new log bucket or stream is created, its retention policy MUST be configured\nin accordance with organisation's data retention policy.", + "Id": "CCC.Logging.CN02.AR01", + "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2504,15 +1730,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined\nin the organisation's data retention policy, it MUST return an empty result.", + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2549,20 +1775,20 @@ ] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to modify or delete data before the object\nlock period expires, then the action MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C03 Enable Object Lock On Log Bucket", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection\nusing object lock on log storage buckets. This prevents logs from being modified\nor deleted during the defined retention period, supporting compliance and forensic\nintegrity.", + "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2593,20 +1819,20 @@ ] }, { - "Id": "CCC.AuditLog.C04.TR01", + "Id": "CCC.AuditLog.CN04.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C04 Restrict Field And Log Type Access", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on log data.\n", + "Recommendation": "Review field level access controls on log data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2642,21 +1868,21 @@ ] }, { - "Id": "CCC.Logging.C05.TR01", - "Description": "When a log storage bucket is created, the bucket's access control settings MUST\nexplicitly deny public read and write access.", + "Id": "CCC.Logging.CN05.AR01", + "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2695,21 +1921,21 @@ ] }, { - "Id": "CCC.Logging.C05.TR02", - "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied\nby bucket policy.", + "Id": "CCC.Logging.CN05.AR02", + "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2745,15 +1971,15 @@ ] }, { - "Id": "CCC.Logging.C06.TR01", - "Description": "When a single principal executes an anomalously high number of log queries,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN06.AR01", + "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C06 Detect and Alert on Potential Log Exfiltration", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", "SubSection": "", - "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt\nto exfiltrate log data.", + "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2793,15 +2019,15 @@ ] }, { - "Id": "CCC.Logging.C07.TR01", - "Description": "When an audit log event is recorded that corresponds to a modification of the logging service\nconfiguration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN07.AR01", + "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C07 Detect and Alert on Log Service Tampering", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", "SubSection": "", - "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified,\nor deleted, indicating a defense evasion attempt.", + "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2850,13 +2076,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR01", + "Id": "CCC.ObjStor.CN01.AR01", "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2910,13 +2136,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR02", + "Id": "CCC.ObjStor.CN01.AR02", "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2970,13 +2196,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR03", + "Id": "CCC.ObjStor.CN01.AR03", "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -3033,13 +2259,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR04", + "Id": "CCC.ObjStor.CN01.AR04", "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -3096,13 +2322,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR01", + "Id": "CCC.ObjStor.CN03.AR01", "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3156,13 +2382,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR02", + "Id": "CCC.ObjStor.CN03.AR02", "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3215,13 +2441,13 @@ ] }, { - "Id": "CCC.ObjStor.C04.TR01", + "Id": "CCC.ObjStor.CN04.AR01", "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3275,13 +2501,13 @@ ] }, { - "Id": "CCC.ObjStor.C04.TR02", + "Id": "CCC.ObjStor.CN04.AR02", "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3334,13 +2560,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR01", + "Id": "CCC.ObjStor.CN05.AR01", "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3393,13 +2619,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR02", + "Id": "CCC.ObjStor.CN05.AR02", "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3454,13 +2680,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR03", + "Id": "CCC.ObjStor.CN05.AR03", "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3514,13 +2740,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR04", + "Id": "CCC.ObjStor.CN05.AR04", "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3574,13 +2800,13 @@ ] }, { - "Id": "CCC.ObjStor.C06.TR01", + "Id": "CCC.ObjStor.CN06.AR01", "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C06 Prevent Deployment in Restricted Regions", + "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", "Applicability": [ @@ -3634,13 +2860,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR01", + "Id": "CCC.ObjStor.CN02.AR01", "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3692,13 +2918,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR02", + "Id": "CCC.ObjStor.CN02.AR02", "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3751,972 +2977,9 @@ "s3_bucket_public_access" ] }, - { - "Id": "CCC.MLDE.CN01.AR01", - "Description": "Verify that only authorized users can access MLDE resources,\nand that access modes are properly defined and enforced.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", - "SubSection": "", - "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE)\nresources is strictly defined and controlled.\nOnly authorized users with appropriate permissions can access these environments,\nmitigating the risk of unauthorized access, data leakage, or service disruption.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.1", - "2013 A.9.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-2", - "AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-01", - "IAM-02" - ] - } - ] - } - ], - "Checks": [ - "accessanalyzer_enabled", - "accessanalyzer_enabled_without_findings", - "iam_root_mfa_enabled", - "iam_root_credentials_management_enabled", - "iam_avoid_root_usage", - "iam_user_mfa_enabled_console_access", - "awslambda_function_not_publicly_accessible", - "awslambda_function_url_public", - "iam_group_administrator_access_policy", - "iam_user_administrator_access_policy", - "iam_policy_attached_only_to_group_or_roles", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_policy_allows_privilege_escalation", - "s3_bucket_public_access", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "s3_account_level_public_access_blocks", - "s3_bucket_cross_account_access" - ] - }, - { - "Id": "CCC.MLDE.CN03.AR01", - "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN03.AR02", - "Description": "For MLDE instances without sensitive data, ensure that root access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_port_ssh_exposed_to_internet", - "ec2_instance_managed_by_ssm", - "ec2_instance_imdsv2_enabled", - "ec2_launch_template_imdsv2_required", - "ec2_instance_account_imdsv2_enabled", - "ec2_instance_public_ip" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR01", - "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_port_ssh_exposed_to_internet", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", - "ec2_instance_public_ip", - "ec2_instance_internet_facing_with_instance_profile" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR02", - "Description": "For MLDE instances without sensitive data, ensure that terminal access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_port_ssh_exposed_to_internet", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_networkacl_allow_ingress_tcp_port_22", - "ec2_instance_public_ip", - "ec2_launch_template_no_public_ip", - "autoscaling_group_launch_configuration_no_public_ip" - ] - }, - { - "Id": "CCC.MLDE.CN02.AR01", - "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN02.AR02", - "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR01", - "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [ - "bedrock_guardrail_prompt_attack_filter_enabled", - "bedrock_guardrail_sensitive_information_filter_enabled", - "bedrock_model_invocation_logging_enabled", - "bedrock_model_invocation_logs_encryption_enabled", - "bedrock_agent_guardrail_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR02", - "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN06.AR01", - "Description": "Verify that automatic scheduled upgrades are enabled on user-managed\nMLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [ - "dms_instance_minor_version_upgrade_enabled", - "elasticache_redis_cluster_auto_minor_version_upgrades", - "memorydb_cluster_auto_minor_version_upgrades", - "mq_broker_auto_minor_version_upgrades", - "redshift_cluster_automatic_upgrades", - "rds_cluster_minor_version_upgrade_enabled", - "rds_instance_minor_version_upgrade_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN06.AR02", - "Description": "Ensure that the upgrade schedule is appropriately configured and\ndoes not interfere with critical operations.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN07.AR01", - "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_public_ip", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports" - ] - }, - { - "Id": "CCC.MLDE.CN07.AR02", - "Description": "For MLDE instances without sensitive data requiring public access,\nensure that appropriate security controls are in place and access is approved.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_public_ip", - "ec2_instance_internet_facing_with_instance_profile", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_postgres_5432", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_mysql_3306", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_oracle_1521_2483", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_ftp_20_21", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_memcached_11211", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_redis_6379", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_elasticsearch_kibana_9200_9300_5601", - "ec2_networkacl_allow_ingress_tcp_port_22", - "ec2_networkacl_allow_ingress_tcp_port_3389", - "ec2_networkacl_allow_ingress_any_port" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR01", - "Description": "Verify that MLDE instances containing sensitive data can only be deployed in\napproved virtual networks with appropriate security controls.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "vpc_subnet_no_public_ip_by_default", - "vpc_subnet_different_az", - "vpc_flow_logs_enabled", - "vpc_endpoint_connections_trust_boundaries", - "vpc_peering_routing_tables_with_least_privilege" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR02", - "Description": "Ensure that MLDE instances without sensitive data are deployed in\nnetworks that meet organizational security standards.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "sagemaker_notebook_instance_without_direct_internet_access_configured", - "sagemaker_training_jobs_network_isolation_enabled", - "sagemaker_models_network_isolation_enabled", - "sagemaker_training_jobs_vpc_settings_configured", - "sagemaker_notebook_instance_vpc_settings_configured", - "sagemaker_notebook_instance_encryption_enabled" - ] - }, - { - "Id": "CCC.Message.CN01.AR01", - "Description": "Attempt to publish a message without using a customer-managed encryption key\nand verify that the message is rejected or not stored.", - "Attributes": [ - { - "FamilyName": "Encryption", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", - "SubSection": "", - "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK)\nto provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "firehose_stream_encrypted_at_rest", - "kinesis_stream_encrypted_at_rest" - ] - }, { "Id": "CCC.Monitor.CN01.AR01", - "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then\nRate Limiting MUST be applied and an Audit Alert MUST be generated.", + "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4769,7 +3032,7 @@ }, { "Id": "CCC.Monitor.CN02.AR01", - "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied\nto reduce the network impact of traffic and an alert must triggered.", + "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4828,14 +3091,14 @@ }, { "Id": "CCC.Monitor.CN03.AR01", - "Description": "When external systems have approved access to internal systems not normally available for public access\nthen they MUST be secured to prevent unauthorised access jumping through to the internal systems and\nonly allow access to specific internal services.", + "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN03 Access External Monitoring", "SubSection": "", - "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to\nensure they don't become an attack path, preventing monitoring systems from forging network requests to\ngain access to internal systems.", + "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4880,7 +3143,7 @@ }, { "Id": "CCC.Monitor.CN04.AR01", - "Description": "When monitoring dashboards display degraded services which may become potential targets then the\ndashboard MUST be protected from unauthorised access.", + "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4930,7 +3193,7 @@ }, { "Id": "CCC.Monitor.CN05.AR01", - "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised\nresponders silence or acknowledge the alert.", + "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4988,7 +3251,7 @@ "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", "SubSection": "", - "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised\nsystem pushing fabricated metrics about a different service", + "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", "Applicability": [ "tlp-clear", "tlp-green", @@ -5030,189 +3293,16 @@ "cloudwatch_log_group_not_publicly_accessible" ] }, - { - "Id": "CCC.SecMgmt.CN01.AR01", - "Description": "Attempt to use an outdated version of a secret after its rotation period\nhas passed and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to\nreduce the risk of secret compromise and unauthorized access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-28" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.SecMgmt.CN02.AR01", - "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per\norganizational data residency and compliance requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03", - "CCC.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.SvlsComp.CN01.AR01", - "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", - "SubSection": "", - "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint,\nallowing it to communicate securely within a virtual private network and preventing\nunauthorized external access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "awslambda_function_url_public", - "awslambda_function_not_publicly_accessible", - "awslambda_function_inside_vpc" - ] - }, - { - "Id": "CCC.SvlsComp.CN02.AR01", - "Description": "Send requests to invoke the function up to the allowed threshold and confirm they\nare successful; then send additional requests exceeding the threshold from the same\nentity and verify that they are denied.", - "Attributes": [ - { - "FamilyName": "Availability", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", - "SubSection": "", - "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity,\npreventing resource exhaustion and denial of service attacks.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH12" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5" - ] - } - ] - } - ], - "Checks": [] - }, { "Id": "CCC.VPC.CN01.AR01", - "Description": "When a subscription is created, the subscription MUST NOT\ncontain default network resources.", + "Description": "When a subscription is created, the subscription MUST NOT contain default network resources.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN01 Restrict Default Network Creation", "SubSection": "", - "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related\nresources during subscription initialization to avoid insecure default\nconfigurations and enforce custom network policies.", + "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related resources during subscription initialization to avoid insecure default configurations and enforce custom network policies.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5260,72 +3350,16 @@ "ec2_securitygroup_allow_ingress_from_internet_to_any_port" ] }, - { - "Id": "CCC.VPC.CN02.AR01", - "Description": "When a resource is created in a public subnet, that resource\nMUST NOT be assigned an external IP address by default.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", - "SubSection": "", - "SubSectionObjective": "Restrict the creation of resources in the public subnet with\ndirect access to the internet to minimize attack surfaces.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [ - "vpc_subnet_no_public_ip_by_default", - "ec2_launch_template_no_public_ip", - "autoscaling_group_launch_configuration_no_public_ip" - ] - }, { "Id": "CCC.VPC.CN03.AR01", - "Description": "When a VPC peering connection is requested, the service MUST\nprevent connections from VPCs that are not explicitly\nallowed.", + "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN03 Restrict VPC Peering to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly\nauthorized destinations to limit network exposure and enforce boundary\ncontrols.", + "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly authorized destinations to limit network exposure and enforce boundary controls.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5374,14 +3408,14 @@ }, { "Id": "CCC.VPC.CN04.AR01", - "Description": "When any network traffic goes to or from an interface in the VPC,\nthe service MUST capture and log all relevant information.", + "Description": "When any network traffic goes to or from an interface in the VPC, the service MUST capture and log all relevant information.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN04 Enforce VPC Flow Logs on VPCs", "SubSection": "", - "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic\ninformation.", + "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic information.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5429,14 +3463,14 @@ }, { "Id": "CCC.Vector.CN01.AR01", - "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it\nmatches expected schema, dimension, and format profiles.", + "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN01 Validate Embeddings Before Indexing", "SubSection": "", - "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated\nbefore indexing to prevent poisoning or corruption.", + "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated before indexing to prevent poisoning or corruption.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5468,14 +3502,14 @@ }, { "Id": "CCC.Vector.CN02.AR01", - "Description": "When an index lifecycle event is triggered, the service MUST\nverify that the actor has explicit permissions for the operation type.", + "Description": "When an index lifecycle event is triggered, the service MUST verify that the actor has explicit permissions for the operation type.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN02 Enforce Role-Based Index Lifecycle Management", "SubSection": "", - "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged\nidentities using fine-grained access controls.", + "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged identities using fine-grained access controls.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5519,14 +3553,14 @@ }, { "Id": "CCC.Vector.CN03.AR01", - "Description": "When a metadata filter is applied to a query, the service MUST\nverify the requester is authorized to access that field.", + "Description": "When a metadata filter is applied to a query, the service MUST verify the requester is authorized to access that field.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN03 Enforce Metadata-Level Access Controls", "SubSection": "", - "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to\nprevent unauthorized exposure or inference.", + "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to prevent unauthorized exposure or inference.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5557,14 +3591,14 @@ }, { "Id": "CCC.Vector.CN04.AR01", - "Description": "When ingestion exceeds pre-defined thresholds, the service MUST\nthrottle or reject excess vector write operations.", + "Description": "When ingestion exceeds pre-defined thresholds, the service MUST throttle or reject excess vector write operations.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN04 Enforce Ingestion Quotas and Throttling", "SubSection": "", - "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by\nrate-limiting vector submissions and enforcing quotas.", + "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by rate-limiting vector submissions and enforcing quotas.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5594,14 +3628,14 @@ }, { "Id": "CCC.Vector.CN05.AR01", - "Description": "When a rollback is attempted, the system MUST log\nthe action and verify rollback authorization.", + "Description": "When a rollback is attempted, the system MUST log the action and verify rollback authorization.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN05 Enforce Index Versioning with Rollback Protection", "SubSection": "", - "SubSectionObjective": "Ensure vector indexes are versioned and that rollback\noperations are authorized and auditable.", + "SubSectionObjective": "Ensure vector indexes are versioned and that rollback operations are authorized and auditable.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5631,14 +3665,14 @@ }, { "Id": "CCC.Vector.CN06.AR01", - "Description": "When an embedding is submitted, the service MUST validate\nthat its format and dimensionality match allowed profiles.", + "Description": "When an embedding is submitted, the service MUST validate that its format and dimensionality match allowed profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN06 Enforce Dimensional and Format Constraints", "SubSection": "", - "SubSectionObjective": "Reject embeddings that do not conform to expected model\nspecifications (dimensions, format, etc).", + "SubSectionObjective": "Reject embeddings that do not conform to expected model specifications (dimensions, format, etc).", "Applicability": [ "tlp-clear", "tlp-green", @@ -5669,14 +3703,14 @@ }, { "Id": "CCC.Vector.CN07.AR01", - "Description": "When a search request is issued, clients MUST be allowed\nto declare their requirement for exact vs approximate results.", + "Description": "When a search request is issued, clients MUST be allowed to declare their requirement for exact vs approximate results.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN07 Support Explicit ANN vs. Exact Search Configuration", "SubSection": "", - "SubSectionObjective": "Provide clients with the option to enforce exact-match\n(non-ANN) search where search fidelity is critical.", + "SubSectionObjective": "Provide clients with the option to enforce exact-match (non-ANN) search where search fidelity is critical.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5697,20 +3731,20 @@ }, { "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic\nMUST include a TLS handshake AND be encrypted using TLS 1.3 or\nhigher.", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not\nalready set, ensure that your services are configured or updated\naccordingly.\n", + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5765,21 +3799,21 @@ }, { "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST\ninclude a SSH handshake AND be encrypted using SSHv2 or higher.", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly\nimplemented SSH server with SSHv2 enabled and configured with\nstrong ciphers.\n", + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5829,20 +3863,20 @@ }, { "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, \nthen it MUST either block the request or automatically\nredirect it to the secure equivalent.", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Review firewall, load balancer, and application configurations to\nensure insecure protocols such as HTTP, FTP, and Telnet are not\nexposed. Where possible, implement automatic redirection to secure\nprotocols such as HTTPS, SFTP, SSH, and regularly scan for\nprotocol drift.\n", + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5896,21 +3930,21 @@ }, { "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol\nand service officially assigned to that port number by the IANA\nService Name and Transport Protocol Port Number Registry, and no\nother, is run on that port.", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number\nRegistry for more information about correct protocol-to-port\nassignments. Avoid running non-standard services on well-known\nports.\n", + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5964,19 +3998,19 @@ }, { "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be\nimplemented to require both client and server certificate\nauthentication for all connections.", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit\nsensitive data. Ensure both client and server certificates are\nvalidated and managed securely. Regularly review certificate\nauthorities and automate certificate rotation where possible.\n", + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6035,21 +4069,21 @@ }, { "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST only use valid, unexpired certificates issued by\na trusted certificate authority.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6069,18 +4103,18 @@ }, { "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 180 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6098,18 +4132,18 @@ }, { "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 90 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6127,21 +4161,21 @@ }, { "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST\nbe included in a list of explicitly trusted or approved locations\nwithin the trust perimeter.", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate the service's deployment\nlocation is included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6186,21 +4220,21 @@ }, { "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability\nzone MUST be included in a list of explicitly trusted or approved\nlocations within the trust perimeter.", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate that child resources can only\nbe deployed to locations included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6245,20 +4279,20 @@ }, { "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete\nand recoverable duplicate that is stored in a physically separate\ndata center.", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement automated data replication processes to ensure that\ndata is consistently duplicated in another region or availability\nzone. Regularly test data recovery from the replicated location to\nensure integrity and availability.\n", + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6305,14 +4339,14 @@ }, { "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST\nbe able to accurately represent the replication locations,\nreplication status, and data synchronization status.", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6358,14 +4392,14 @@ }, { "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource\nlogs MUST NOT be accessible from the resource they record access\nto.", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", @@ -6420,21 +4454,21 @@ }, { "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service\nor its child resources MUST NOT be possible without also disabling\nthe corresponding resource.", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging mechanisms are tightly integrated with\nservice operations, so that logging cannot be disabled without\nstopping the service itself.\n", + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6486,19 +4520,19 @@ }, { "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for\nthe service or its child resources MUST NOT be possible without\nhalting operation of the corresponding resource and publishing\ncorresponding events to monitored channels.", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should result in the redirection of\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging configurations are immutable during\nservice operation so that any changes require stopping the service\nand publishing corresponding events to monitored channels.\n", + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6554,14 +4588,14 @@ }, { "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication\nonly occurs to destinations that are explicitly included within\nthe defined trust perimeter.", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations\nthat are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6607,14 +4641,14 @@ }, { "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest\nindustry-standard encryption methods.", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN02 Encrypt Data for Storage", "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong\nencryption algorithms.", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6676,14 +4710,14 @@ }, { "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that\nall encryption keys use the latest industry-standard cryptographic\nalgorithms.", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6737,14 +4771,14 @@ }, { "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 180 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber" ], @@ -6797,14 +4831,14 @@ }, { "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that\ncustomer-managed encryption keys (CMEKs) are used.", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "", "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6858,14 +4892,14 @@ }, { "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that\naccess to encryption keys is restricted to authorized personnel\nand services, following the principle of least privilege.", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green", @@ -6921,14 +4955,14 @@ }, { "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 365 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green" @@ -6980,14 +5014,14 @@ }, { "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 90 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-red" ], @@ -7037,21 +5071,21 @@ }, { "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the\nstorage mechanism MUST NOT allow modification or deletion\nwithin 30 days of creation.", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup\nretention policies that enforce a minimum retention period of 30\ndays.\n", + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7069,20 +5103,20 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n30 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 30 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7109,18 +5143,18 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n14 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-red" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 14 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7143,14 +5177,14 @@ }, { "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user\ninterface, the authentication process MUST require multiple\nidentifying factors for authentication.", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7211,14 +5245,14 @@ }, { "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API\nendpoint, the authentication process MUST require a credential\nsuch as an API key or token AND originate from within the trust\nperimeter.", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7279,14 +5313,14 @@ }, { "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through\na user interface, the authentication process MUST require multiple\nidentifying factors from the user.", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7346,14 +5380,14 @@ }, { "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through\nan API endpoint, the authentication process MUST require a\ncredential such as an API key or token AND originate from within\nthe trust perimeter.", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7414,14 +5448,14 @@ }, { "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child\nresource, the service MUST block requests from unauthorized\nentities.", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7496,14 +5530,14 @@ }, { "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST refuse requests\nfrom unauthorized entities.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7581,14 +5615,14 @@ }, { "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource in a multi-tenant environment, the\nservice MUST refuse requests across tenant boundaries unless the\norigin is explicitly included in a pre-approved allowlist.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7648,14 +5682,14 @@ }, { "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "", "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7722,14 +5756,14 @@ }, { "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter,\nthe service MUST NOT provide any response that may indicate the\nservice exists.", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-red" ], @@ -7797,14 +5831,14 @@ }, { "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-green", "tlp-amber", @@ -7870,14 +5904,14 @@ }, { "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST log the client\nidentity, time, and result of the attempt.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7943,14 +5977,14 @@ }, { "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-amber", "tlp-red" @@ -8004,14 +6038,14 @@ }, { "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-red" ], @@ -8068,19 +6102,19 @@ }, { "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish\nan event to a monitored channel which includes the client\nidentity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns\nindicative of enumeration activities, such as repeated access\nattempts, requests, or liveness probes. Configure alerts to notify\nsecurity teams of any activities that merit further investigation.\n", + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -8118,21 +6152,21 @@ }, { "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the\nclient identity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration\nactivities, including client identity, timestamps, and activity\nnature. Retain logs according to organizational policies, and\noccasionally review them for patterns that may indicate\nreconnaissance activities.\n", + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", diff --git a/prowler/compliance/azure/ccc_azure.json b/prowler/compliance/azure/ccc_azure.json index efc2188406..004137ad53 100644 --- a/prowler/compliance/azure/ccc_azure.json +++ b/prowler/compliance/azure/ccc_azure.json @@ -6,19 +6,19 @@ "Description": "Common Cloud Controls Catalog (CCC) for Azure", "Requirements": [ { - "Id": "CCC.AuditLog.C01.TR01", + "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature.\n", + "Recommendation": "Ensure hash of data is included in digital signature. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -47,19 +47,19 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.C01.TR02", + "Id": "CCC.AuditLog.CN01.AR02", "Description": "When the signature validation process is performed, then it MUST detect any missing (deleted) log file.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function.\n", + "Recommendation": "Ensure verification process includes a chained hash function. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -88,15 +88,15 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.C02.TR01", - "Description": "When a manual action is performed to generate each audit log type,\nthen the corresponding audit log type MUST be generated and recorded.", + "Id": "CCC.AuditLog.CN02.AR01", + "Description": "When a manual action is performed to generate each audit log type, then the corresponding audit log type MUST be generated and recorded.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C02 Enable And Validate All Audit Log Types", + "Section": "CCC.AuditLog.CN02 Enable And Validate All Audit Log Types", "SubSection": "", - "SubSectionObjective": "Review audit log configuration and ensure that all audit log types\nare being generated and replicated to configured sinks", + "SubSectionObjective": "Review audit log configuration and ensure that all audit log types are being generated and replicated to configured sinks", "Applicability": [ "tlp-red", "tlp-amber" @@ -136,20 +136,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR01", + "Id": "CCC.AuditLog.CN03.AR01", "Description": "When an attempt is made to disable a log source, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -183,20 +183,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR02", - "Description": "When an attempt is made to alter the retention or object lock status\nof an external data log source or bucket, then an alert MUST be generated.", + "Id": "CCC.AuditLog.CN03.AR02", + "Description": "When an attempt is made to alter the retention or object lock status of an external data log source or bucket, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -240,20 +240,20 @@ ] }, { - "Id": "CCC.AuditLog.C04.TR01", - "Description": "When audit log buckets are created then verify that server access\nlogging MUST be enabled for the audit log bucket,\nwith logs delivered to a separate, secure logging bucket.", + "Id": "CCC.AuditLog.CN04.AR01", + "Description": "When audit log buckets are created then verify that server access logging MUST be enabled for the audit log bucket, with logs delivered to a separate, secure logging bucket.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C04 Ensure Access Logging Is Enabled on the Audit Log Bucket", + "Section": "CCC.AuditLog.CN04 Ensure Access Logging Is Enabled on the Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to\ncapture all requests made to the bucket, providing an audit trail of data access.", + "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to capture all requests made to the bucket, providing an audit trail of data access.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging.\nEnsure the target logging bucket is configured for appropriate security,\nincluding restricted access and immutability.\n", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -289,20 +289,20 @@ ] }, { - "Id": "CCC.AuditLog.C05.TR01", + "Id": "CCC.AuditLog.CN05.AR01", "Description": "When audit logs are exported, then audit logs MUST be present in the configured data location.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C05 Export Audit Logs To Bucket", + "Section": "CCC.AuditLog.CN05 Export Audit Logs To Bucket", "SubSection": "", - "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated\nand can be subject to greater access control and data retention polices.", + "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated and can be subject to greater access control and data retention polices.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting.\n", + "Recommendation": "Configure audit log exporting. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -337,21 +337,21 @@ ] }, { - "Id": "CCC.AuditLog.C06.TR01", - "Description": "When the retention policy is applied, then data MUST\nbe automatically deleted after the configured number of days.", + "Id": "CCC.AuditLog.CN06.AR01", + "Description": "When the retention policy is applied, then data MUST be automatically deleted after the configured number of days.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C06 Enforce Retention Policy on Audit Log Bucket", + "Section": "CCC.AuditLog.CN06 Enforce Retention Policy on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are\nretained for the correct number of days as defined by your organization's policy.", + "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are retained for the correct number of days as defined by your organization's policy.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce\nthe required data retention period.\n", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -384,21 +384,21 @@ ] }, { - "Id": "CCC.AuditLog.C07.TR01", - "Description": "When a standard file deletion is attempted on an object within\nthe audit log bucket, then it MUST be prevented unless MFA is provided.", + "Id": "CCC.AuditLog.CN07.AR01", + "Description": "When a standard file deletion is attempted on an object within the audit log bucket, then it MUST be prevented unless MFA is provided.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C07 Enforce MFA Delete on Audit Log Bucket", + "Section": "CCC.AuditLog.CN07 Enforce MFA Delete on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to\nprovide greater protection against accidental or malicious deletion of audit data.", + "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to provide greater protection against accidental or malicious deletion of audit data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations)\non the audit log bucket.\n", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -428,20 +428,20 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to delete data before the object\nlock period expires, then the deletion MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to delete data before the object lock period expires, then the deletion MUST be denied.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C08 Enable Object Lock On Audit Log Bucket", + "Section": "CCC.AuditLog.CN08 Enable Object Lock On Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket.\nThe lock time MUST be configured to meet your organization, legal and compliance goals.\nDeletion attempts before the lock period MUST be denied.", + "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket. The lock time MUST be configured to meet your organization, legal and compliance goals. Deletion attempts before the lock period MUST be denied.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -475,20 +475,20 @@ ] }, { - "Id": "CCC.AuditLog.C09.TR01", + "Id": "CCC.AuditLog.CN09.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C09 Restrict Field And Log Type Access", + "Section": "CCC.AuditLog.CN09 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data.\n", + "Recommendation": "Review field level access controls on audit data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -525,21 +525,21 @@ ] }, { - "Id": "CCC.AuditLog.C10.TR01", - "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny\npublic read and write access.", + "Id": "CCC.AuditLog.CN10.AR01", + "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -573,21 +573,21 @@ ] }, { - "Id": "CCC.AuditLog.C10.TR02", - "Description": "When the URL of a audit log storage bucket's object is accessed publicly then,\nit should be denied by bucket policy.", + "Id": "CCC.AuditLog.CN10.AR02", + "Description": "When the URL of a audit log storage bucket's object is accessed publicly then, it should be denied by bucket policy.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -622,15 +622,15 @@ ] }, { - "Id": "CCC.Build.C01.TR01", + "Id": "CCC.Build.CN01.AR01", "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", "Attributes": [ { "FamilyName": "Access Control", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C01 Restrict Allowed Build Agents", + "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", "SubSection": "", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain\ncontrol over the build environment and prevent unauthorized code execution.", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", "Applicability": [ "tlp-red", "tlp-amber" @@ -664,15 +664,15 @@ "Checks": [] }, { - "Id": "CCC.Build.C02.TR01", - "Description": "Attempt to trigger a build from an unauthorized external service or\nrepository and verify that the build does not start.", + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", "Attributes": [ { "FamilyName": "Access Control", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C02 Restrict Allowed External Services for Build Triggers", + "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", "SubSection": "", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or\nrepositories to prevent unauthorized code execution or tampering.", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", "Applicability": [ "tlp-red", "tlp-amber" @@ -706,15 +706,15 @@ "Checks": [] }, { - "Id": "CCC.Build.C03.TR01", + "Id": "CCC.Build.CN03.AR01", "Description": "Attempt to access the build environment from an external network and verify that access is denied.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.C03 Deny External Network Access for Build Environments", + "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", "SubSection": "", - "SubSectionObjective": "Ensure that build environments do not have external network access to\nprevent unauthorized external access and data exfiltration.", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", "Applicability": [ "tlp-red", "tlp-amber" @@ -758,15 +758,15 @@ ] }, { - "Id": "CCC.CntrReg.C01.TR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry\nand observe if it is flagged or rejected by the vulnerability scanning process.", + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", "Attributes": [ { "FamilyName": "Risk Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C01 Implement Vulnerability Scanning for Artifacts", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for\nvulnerabilities to identify and remediate security issues before deployment.", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", "Applicability": [ "tlp-red", "tlp-amber" @@ -803,60 +803,15 @@ ] }, { - "Id": "CCC.CntrReg.C02.TR01", - "Description": "Confirm that artifacts older than the specified retention period are automatically\ndeleted from the registry.", - "Attributes": [ - { - "FamilyName": "Data Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C02 Implement Cleanup Policies for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to\nmanage storage effectively and reduce security risks associated with outdated versions.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-12" - ] - } - ] - } - ], - "Checks": [ - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled" - ] - }, - { - "Id": "CCC.DataWar.C01.TR01", - "Description": "Attempt to access underlying database tables directly without\nusing managed views and verify that access is denied.", + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C01 Enforce Use of Managed Views for Data Access", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users\nfrom accessing underlying tables directly and enforcing consistent security policies.", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", "Applicability": [ "tlp-red", "tlp-amber" @@ -890,15 +845,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C02.TR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and\nverify that access is denied or data is masked.", + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C02 Enforce Column-Level Security Policies", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles,\npreventing unauthorized access to sensitive information.", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", "Applicability": [ "tlp-red", "tlp-amber" @@ -932,15 +887,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C03.TR01", - "Description": "Attempt to query data rows that the user should not have access to and verify\nthat access is denied or data is not returned.", + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C03 Enforce Row-Level Security Policies", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes,\npreventing unauthorized access to specific subsets of data.", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", "Applicability": [ "tlp-red", "tlp-amber" @@ -980,684 +935,13 @@ ] }, { - "Id": "CCC.GenAI.C01.TR01", - "Description": "Untrusted input such as user queries, RAG data or tool output\nMUST be validated before it is passed to a GenAI model.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C01.TR02", - "Description": "If malicious patterns such as prompt injection or sensitive\ndata are detected during input validation, the input MUST\nbe blocked or sanitised.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C02.TR01", - "Description": "GenAI model output MUST be validated for format conformance,\nmalicious patterns, sensitive data and inapropriate content\nbefore being passed to users, application or plugins.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C02.TR02", - "Description": "In the event of policy violations, the AI-generated content MUST\nbe redacted, encoded or rejected.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] - }, - { - "Id": "CCC.GenAI.C03.TR01", - "Description": "When data is designated for model training or RAG ingestion, then its\nsource MUST be explicitly approved and its provenance documented.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C03.TR02", - "Description": "Data from unvetted sources MUST NOT be used in production systems.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C04.TR01", - "Description": "When data is ingested for training, fine-tuning or conversion\nto vector embeddings, it MUST be validated for sensitive\ninformation or malicious content.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C04.TR02", - "Description": "If sensitive data or malicious content is detected, it must\nbe rejected, redacted or flagged for manual review.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C05.TR01", - "Description": "When a RAG-enabled system generates a response containing information\nretrieved from its knowledge base, then the response MUST include a\nverifiable citation that links back to the specific source document.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C05 Citations and Source Traceability", - "SubSection": "", - "SubSectionObjective": "Require the GenAI system to provide citations or direct links\nback to the source documents used to generate a response, in\nto enhance the transparency, trustworthiness, and verifiability\nof AI-generated content.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH09", - "CCC.GenAI.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-DET-013" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "keyvault_logging_enabled", - "app_http_logs_enabled", - "app_function_application_insights_enabled", - "appinsights_ensure_is_configured", - "monitor_alert_service_health_exists", - "sqlserver_auditing_enabled", - "sqlserver_auditing_retention_90_days", - "mysql_flexible_server_audit_log_enabled" - ] - }, - { - "Id": "CCC.GenAI.C06.TR01", - "Description": "When an LLM invokes an external tool (e.g., an API, a plugin),\nthen the tool MUST operate with the least privileges required\nfor performing its intended functionality.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", - "Section": "CCC.GenAI.C06 Least Privilege for Plugins", - "SubSection": "", - "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system\ncan call to limit the potential damage if an agent is coerced\nto perform unintended actions or vulnerabilities in the tools\nare exploited.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH07", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Agent Permissions" - ] - } - ] - } - ], - "Checks": [ - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_subscription_roles_owner_custom_not_created", - "iam_role_user_access_admin_restricted", - "app_function_identity_without_admin_privileges", - "app_function_identity_is_configured", - "cosmosdb_account_use_aad_and_rbac" - ] - }, - { - "Id": "CCC.GenAI.C07.TR01", - "Description": "When an application makes an API call to a foundational model in a\nproduction environment, then it MUST specify an explicit version\nidentifier.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "The Configuration Management control family involves establishing,\nmaintaining and monitoring the configuration of the service and\nrelated applications and infrastructure to ensure consistency,\nsecure defaults and compliance.\n", - "Section": "CCC.GenAI.C07 Model Version Pinning", - "SubSection": "", - "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific,\ntested version of a foundational model to prevent unexpected\nbehaviour changes introduced by provider-side updates.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-010" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C08.TR01", - "Description": "When a new AI model is considered for production deployment, it\nMUST undergo a formal red teaming and quality assurance review.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] - }, - { - "Id": "CCC.GenAI.C08.TR02", - "Description": "If model quality review or red teaming identifies an issue that exceeds\nthe organization's risk tolerance, the model MUST NOT be deployed until\nthe issue is remediated.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] - }, - { - "Id": "CCC.KeyMgmt.C01.TR01", - "Description": "When a key version is scheduled for deletion or disabled, an\nalert MUST be generated within five minutes.", + "Id": "CCC.KeyMgmt.CN01.AR01", + "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", "Attributes": [ { "FamilyName": "Logging and Metrics Publication", "FamilyDescription": "Controls that collect, alert, and retain key-management events.", - "Section": "CCC.KeyMgmt.C01 Alert on Key-version Changes", + "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", "SubSection": "", "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", "Applicability": [ @@ -1700,13 +984,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C02.TR01", - "Description": "When IAM roles and key policies are reviewed, Decrypt permission\nMUST be granted exclusively to documented authorised principals.", + "Id": "CCC.KeyMgmt.CN02.AR01", + "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", - "Section": "CCC.KeyMgmt.C02 Limit Decrypt Permissions", + "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", "SubSection": "", "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", "Applicability": [ @@ -1745,13 +1029,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C03.TR01", - "Description": "When rotation settings are examined, rotation MUST be enabled with\nan interval not exceeding 365 days.", + "Id": "CCC.KeyMgmt.CN03.AR01", + "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C03 Enforce Automatic Rotation", + "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", "SubSection": "", "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", "Applicability": [ @@ -1787,13 +1071,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C04.TR01", - "Description": "When a key import request is processed, the key MUST use an\napproved algorithm (RSA-2048+, EC-P256+) and originate from a\ncertified HSM.", + "Id": "CCC.KeyMgmt.CN04.AR01", + "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C04 Validate Imported Keys", + "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", "SubSection": "", "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", "Applicability": [ @@ -1838,13 +1122,13 @@ ] }, { - "Id": "CCC.LB.C01.TR01", - "Description": "When a single client sends more than 2000 requests within any\n5-minute sliding window, the load balancer MUST throttle all\nsubsequent requests from that client for at least 60 seconds.", + "Id": "CCC.LB.CN01.AR01", + "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1852,7 +1136,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via\nsynthetic traffic tests.\n", + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1885,13 +1169,13 @@ "Checks": [] }, { - "Id": "CCC.LB.C01.TR02", - "Description": "When throttling is invoked, the load balancer MUST\nrecord the event in the access log within 5 minutes\nfor alerting and trend analysis.", + "Id": "CCC.LB.CN01.AR02", + "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1899,7 +1183,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Enable access logging and configure metric filters\non HTTP 429 counts to trigger alerts.\n", + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1939,21 +1223,21 @@ ] }, { - "Id": "CCC.LB.C06.TR01", - "Description": "When more than 10 percent of targets change from healthy to\nunhealthy within five minutes, an alert MUST be issued.", + "Id": "CCC.LB.CN06.AR01", + "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C06 Secure Health-Check Telemetry", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", "SubSection": "", - "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on\nabnormal status changes.", + "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Instrument metrics for health check results and target\nremoval events. Configure monitoring alarms to alert\non abnormal spikes in unhealthy targets.\n", + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1992,21 +1276,21 @@ ] }, { - "Id": "CCC.LB.C04.TR01", - "Description": "When routing weights change, the request MUST originate\nfrom an explicitly defined and trusted identity and MUST\nbe logged.", + "Id": "CCC.LB.CN04.AR01", + "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C04 Enforce Distribution Policies", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN04 Enforce Distribution Policies", "SubSection": "", - "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified\nonly by trusted identities.", + "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Define a list of trusted principals allowed to modify\nrouting configurations. Enforce via conditional access\npolicies, and log changes using audit logging.\n", + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2044,15 +1328,15 @@ ] }, { - "Id": "CCC.LB.C05.TR01", - "Description": "When stickiness is enabled, session cookies MUST expire\nwithin 30 minutes of inactivity.", + "Id": "CCC.LB.CN05.AR01", + "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C05 Validate Session Affinity", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN05 Validate Session Affinity", "SubSection": "", - "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking\nrisks.", + "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2090,15 +1374,15 @@ ] }, { - "Id": "CCC.LB.C09.TR01", - "Description": "When an API call originates outside the approved CIDR\nset, the request MUST be denied.", + "Id": "CCC.LB.CN09.AR01", + "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C09 Restrict Management API Access", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN09 Restrict Management API Access", "SubSection": "", - "SubSectionObjective": "Limit load-balancer API calls to authorised identities and\ntrusted networks.", + "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2138,15 +1422,15 @@ ] }, { - "Id": "CCC.LB.C02.TR01", - "Description": "When concurrent connections reach 80 percent of capacity, the\nautoscaling group MUST add at least one instance within five\nminutes.", + "Id": "CCC.LB.CN02.AR01", + "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C02 Auto-Scale Load Balancer Capacity", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", "SubSection": "", - "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic\nspikes.", + "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2180,15 +1464,15 @@ "Checks": [] }, { - "Id": "CCC.LB.C07.TR01", - "Description": "When responses pass through the load balancer, the\n\"Server\" header MUST be replaced with \"lb\".", + "Id": "CCC.LB.CN07.AR01", + "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C07 Scrub Sensitive Headers", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN07 Scrub Sensitive Headers", "SubSection": "", - "SubSectionObjective": "Remove headers that disclose internal details or software\nversions from HTTP responses.", + "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2222,15 +1506,15 @@ "Checks": [] }, { - "Id": "CCC.LB.C08.TR01", - "Description": "When a certificate is within 30 days of expiry, automated renewal\nMUST complete and deploy a new certificate within 24 hours.", + "Id": "CCC.LB.CN08.AR01", + "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", "Attributes": [ { "FamilyName": "Encryption", "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", - "Section": "CCC.LB.C08 Automate Certificate Renewal", + "Section": "CCC.LB.CN08 Automate Certificate Renewal", "SubSection": "", - "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and\ndeployment before expiry.", + "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2261,21 +1545,18 @@ ] } ], - "Checks": [ - "app_minimum_tls_version_12", - "storage_ensure_minimum_tls_version_12" - ] + "Checks": [] }, { - "Id": "CCC.Logging.C01.TR01", - "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be\nenabled by default and directed to the central sink.", + "Id": "CCC.Logging.CN01.AR01", + "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2317,15 +1598,15 @@ ] }, { - "Id": "CCC.Logging.C01.TR02", - "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant\nlogs (e.g., OS, application, service logs) to the central log sink.", + "Id": "CCC.Logging.CN01.AR02", + "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2367,15 +1648,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR01", - "Description": "When a new log bucket or stream is created, its retention policy MUST be configured\nin accordance with organisation's data retention policy.", + "Id": "CCC.Logging.CN02.AR01", + "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2412,15 +1693,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined\nin the organisation's data retention policy, it MUST return an empty result.", + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2459,20 +1740,20 @@ ] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to modify or delete data before the object\nlock period expires, then the action MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C03 Enable Object Lock On Log Bucket", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection\nusing object lock on log storage buckets. This prevents logs from being modified\nor deleted during the defined retention period, supporting compliance and forensic\nintegrity.", + "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2501,20 +1782,20 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.C04.TR01", + "Id": "CCC.AuditLog.CN04.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C04 Restrict Field And Log Type Access", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on log data.\n", + "Recommendation": "Review field level access controls on log data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2552,21 +1833,21 @@ ] }, { - "Id": "CCC.Logging.C05.TR01", - "Description": "When a log storage bucket is created, the bucket's access control settings MUST\nexplicitly deny public read and write access.", + "Id": "CCC.Logging.CN05.AR01", + "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2600,21 +1881,21 @@ ] }, { - "Id": "CCC.Logging.C05.TR02", - "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied\nby bucket policy.", + "Id": "CCC.Logging.CN05.AR02", + "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2649,15 +1930,15 @@ ] }, { - "Id": "CCC.Logging.C06.TR01", - "Description": "When a single principal executes an anomalously high number of log queries,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN06.AR01", + "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C06 Detect and Alert on Potential Log Exfiltration", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", "SubSection": "", - "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt\nto exfiltrate log data.", + "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2696,15 +1977,15 @@ ] }, { - "Id": "CCC.Logging.C07.TR01", - "Description": "When an audit log event is recorded that corresponds to a modification of the logging service\nconfiguration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN07.AR01", + "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C07 Detect and Alert on Log Service Tampering", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", "SubSection": "", - "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified,\nor deleted, indicating a defense evasion attempt.", + "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2755,13 +2036,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR01", + "Id": "CCC.ObjStor.CN01.AR01", "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2815,13 +2096,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR02", + "Id": "CCC.ObjStor.CN01.AR02", "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2877,13 +2158,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR03", + "Id": "CCC.ObjStor.CN01.AR03", "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2940,13 +2221,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR04", + "Id": "CCC.ObjStor.CN01.AR04", "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -3001,13 +2282,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR01", + "Id": "CCC.ObjStor.CN03.AR01", "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3060,13 +2341,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR02", + "Id": "CCC.ObjStor.CN03.AR02", "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3116,13 +2397,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C04.TR01", + "Id": "CCC.ObjStor.CN04.AR01", "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3176,13 +2457,13 @@ ] }, { - "Id": "CCC.ObjStor.C04.TR02", + "Id": "CCC.ObjStor.CN04.AR02", "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3235,13 +2516,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR01", + "Id": "CCC.ObjStor.CN05.AR01", "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3293,13 +2574,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR02", + "Id": "CCC.ObjStor.CN05.AR02", "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3351,13 +2632,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR03", + "Id": "CCC.ObjStor.CN05.AR03", "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3409,13 +2690,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR04", + "Id": "CCC.ObjStor.CN05.AR04", "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3467,13 +2748,13 @@ ] }, { - "Id": "CCC.ObjStor.C06.TR01", + "Id": "CCC.ObjStor.CN06.AR01", "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C06 Prevent Deployment in Restricted Regions", + "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", "Applicability": [ @@ -3528,13 +2809,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR01", + "Id": "CCC.ObjStor.CN02.AR01", "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3588,13 +2869,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR02", + "Id": "CCC.ObjStor.CN02.AR02", "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3648,1001 +2929,9 @@ "storage_ensure_private_endpoints_in_storage_accounts" ] }, - { - "Id": "CCC.MLDE.CN01.AR01", - "Description": "Verify that only authorized users can access MLDE resources,\nand that access modes are properly defined and enforced.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", - "SubSection": "", - "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE)\nresources is strictly defined and controlled.\nOnly authorized users with appropriate permissions can access these environments,\nmitigating the risk of unauthorized access, data leakage, or service disruption.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.1", - "2013 A.9.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-2", - "AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-01", - "IAM-02" - ] - } - ] - } - ], - "Checks": [ - "aks_cluster_rbac_enabled", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled", - "app_ensure_auth_is_set_up", - "app_register_with_identity", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_function_not_publicly_accessible", - "app_function_access_keys_configured", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "entra_policy_user_consent_for_verified_apps", - "entra_security_defaults_enabled", - "entra_trusted_named_locations_exists", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_policy_default_users_cannot_create_security_groups", - "entra_policy_ensure_default_user_cannot_create_apps", - "entra_users_cannot_create_microsoft_365_groups" - ] - }, - { - "Id": "CCC.MLDE.CN03.AR01", - "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled", - "app_function_not_publicly_accessible", - "app_function_identity_without_admin_privileges", - "iam_role_user_access_admin_restricted", - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication", - "app_function_identity_is_configured" - ] - }, - { - "Id": "CCC.MLDE.CN03.AR02", - "Description": "For MLDE instances without sensitive data, ensure that root access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication", - "app_function_identity_without_admin_privileges", - "app_function_identity_is_configured", - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "entra_global_admin_in_less_than_five_users", - "entra_privileged_user_has_mfa", - "entra_conditional_access_policy_require_mfa_for_management_api" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR01", - "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR02", - "Description": "For MLDE instances without sensitive data, ensure that terminal access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication" - ] - }, - { - "Id": "CCC.MLDE.CN02.AR01", - "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN02.AR02", - "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR01", - "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [ - "vm_ensure_using_approved_images", - "vm_desired_sku_size", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR02", - "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [ - "vm_ensure_using_approved_images" - ] - }, - { - "Id": "CCC.MLDE.CN06.AR01", - "Description": "Verify that automatic scheduled upgrades are enabled on user-managed\nMLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [ - "defender_ensure_system_updates_are_applied" - ] - }, - { - "Id": "CCC.MLDE.CN06.AR02", - "Description": "Ensure that the upgrade schedule is appropriately configured and\ndoes not interfere with critical operations.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN07.AR01", - "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_blob_public_access_level_is_disabled", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_default_network_access_rule_is_denied", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "network_public_ip_shodan" - ] - }, - { - "Id": "CCC.MLDE.CN07.AR02", - "Description": "For MLDE instances without sensitive data requiring public access,\nensure that appropriate security controls are in place and access is approved.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_default_network_access_rule_is_denied", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR01", - "Description": "Verify that MLDE instances containing sensitive data can only be deployed in\napproved virtual networks with appropriate security controls.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled", - "app_function_vnet_integration_enabled", - "app_function_not_publicly_accessible", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_default_network_access_rule_is_denied", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR02", - "Description": "Ensure that MLDE instances without sensitive data are deployed in\nnetworks that meet organizational security standards.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled", - "app_function_vnet_integration_enabled", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_default_network_access_rule_is_denied", - "storage_ensure_azure_services_are_trusted_to_access_is_enabled", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted" - ] - }, - { - "Id": "CCC.Message.CN01.AR01", - "Description": "Attempt to publish a message without using a customer-managed encryption key\nand verify that the message is rejected or not stored.", - "Attributes": [ - { - "FamilyName": "Encryption", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", - "SubSection": "", - "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK)\nto provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] - }, { "Id": "CCC.Monitor.CN01.AR01", - "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then\nRate Limiting MUST be applied and an Audit Alert MUST be generated.", + "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4698,7 +2987,7 @@ }, { "Id": "CCC.Monitor.CN02.AR01", - "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied\nto reduce the network impact of traffic and an alert must triggered.", + "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4745,14 +3034,14 @@ }, { "Id": "CCC.Monitor.CN03.AR01", - "Description": "When external systems have approved access to internal systems not normally available for public access\nthen they MUST be secured to prevent unauthorised access jumping through to the internal systems and\nonly allow access to specific internal services.", + "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN03 Access External Monitoring", "SubSection": "", - "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to\nensure they don't become an attack path, preventing monitoring systems from forging network requests to\ngain access to internal systems.", + "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4793,7 +3082,7 @@ }, { "Id": "CCC.Monitor.CN04.AR01", - "Description": "When monitoring dashboards display degraded services which may become potential targets then the\ndashboard MUST be protected from unauthorised access.", + "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4838,7 +3127,7 @@ }, { "Id": "CCC.Monitor.CN05.AR01", - "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised\nresponders silence or acknowledge the alert.", + "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4889,7 +3178,7 @@ "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", "SubSection": "", - "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised\nsystem pushing fabricated metrics about a different service", + "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", "Applicability": [ "tlp-clear", "tlp-green", @@ -4930,201 +3219,16 @@ "app_ensure_auth_is_set_up" ] }, - { - "Id": "CCC.SecMgmt.CN01.AR01", - "Description": "Attempt to use an outdated version of a secret after its rotation period\nhas passed and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to\nreduce the risk of secret compromise and unauthorized access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled", - "storage_key_rotation_90_days" - ] - }, - { - "Id": "CCC.SecMgmt.CN02.AR01", - "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per\norganizational data residency and compliance requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03", - "CCC.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_rbac_enabled", - "keyvault_logging_enabled" - ] - }, - { - "Id": "CCC.SvlsComp.CN01.AR01", - "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", - "SubSection": "", - "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint,\nallowing it to communicate securely within a virtual private network and preventing\nunauthorized external access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "app_function_not_publicly_accessible", - "app_function_vnet_integration_enabled" - ] - }, - { - "Id": "CCC.SvlsComp.CN02.AR01", - "Description": "Send requests to invoke the function up to the allowed threshold and confirm they\nare successful; then send additional requests exceeding the threshold from the same\nentity and verify that they are denied.", - "Attributes": [ - { - "FamilyName": "Availability", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", - "SubSection": "", - "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity,\npreventing resource exhaustion and denial of service attacks.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH12" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5" - ] - } - ] - } - ], - "Checks": [ - "app_function_access_keys_configured", - "app_function_not_publicly_accessible", - "app_function_vnet_integration_enabled", - "app_function_identity_is_configured" - ] - }, { "Id": "CCC.VPC.CN01.AR01", - "Description": "When a subscription is created, the subscription MUST NOT\ncontain default network resources.", + "Description": "When a subscription is created, the subscription MUST NOT contain default network resources.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN01 Restrict Default Network Creation", "SubSection": "", - "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related\nresources during subscription initialization to avoid insecure default\nconfigurations and enforce custom network policies.", + "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related resources during subscription initialization to avoid insecure default configurations and enforce custom network policies.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5168,86 +3272,16 @@ ], "Checks": [] }, - { - "Id": "CCC.VPC.CN02.AR01", - "Description": "When a resource is created in a public subnet, that resource\nMUST NOT be assigned an external IP address by default.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", - "SubSection": "", - "SubSectionObjective": "Restrict the creation of resources in the public subnet with\ndirect access to the internet to minimize attack surfaces.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_public_access_disabled", - "aks_clusters_created_with_private_nodes", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "cosmosdb_account_use_private_endpoints", - "cosmosdb_account_firewall_use_selected_networks", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_default_network_access_rule_is_denied", - "storage_blob_public_access_level_is_disabled", - "sqlserver_unrestricted_inbound_access", - "app_function_not_publicly_accessible" - ] - }, { "Id": "CCC.VPC.CN03.AR01", - "Description": "When a VPC peering connection is requested, the service MUST\nprevent connections from VPCs that are not explicitly\nallowed.", + "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN03 Restrict VPC Peering to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly\nauthorized destinations to limit network exposure and enforce boundary\ncontrols.", + "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly authorized destinations to limit network exposure and enforce boundary controls.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5294,14 +3328,14 @@ }, { "Id": "CCC.VPC.CN04.AR01", - "Description": "When any network traffic goes to or from an interface in the VPC,\nthe service MUST capture and log all relevant information.", + "Description": "When any network traffic goes to or from an interface in the VPC, the service MUST capture and log all relevant information.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN04 Enforce VPC Flow Logs on VPCs", "SubSection": "", - "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic\ninformation.", + "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic information.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5351,14 +3385,14 @@ }, { "Id": "CCC.Vector.CN01.AR01", - "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it\nmatches expected schema, dimension, and format profiles.", + "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN01 Validate Embeddings Before Indexing", "SubSection": "", - "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated\nbefore indexing to prevent poisoning or corruption.", + "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated before indexing to prevent poisoning or corruption.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5390,14 +3424,14 @@ }, { "Id": "CCC.Vector.CN02.AR01", - "Description": "When an index lifecycle event is triggered, the service MUST\nverify that the actor has explicit permissions for the operation type.", + "Description": "When an index lifecycle event is triggered, the service MUST verify that the actor has explicit permissions for the operation type.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN02 Enforce Role-Based Index Lifecycle Management", "SubSection": "", - "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged\nidentities using fine-grained access controls.", + "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged identities using fine-grained access controls.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5435,14 +3469,14 @@ }, { "Id": "CCC.Vector.CN03.AR01", - "Description": "When a metadata filter is applied to a query, the service MUST\nverify the requester is authorized to access that field.", + "Description": "When a metadata filter is applied to a query, the service MUST verify the requester is authorized to access that field.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN03 Enforce Metadata-Level Access Controls", "SubSection": "", - "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to\nprevent unauthorized exposure or inference.", + "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to prevent unauthorized exposure or inference.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5469,49 +3503,18 @@ ] } ], - "Checks": [ - "app_function_not_publicly_accessible", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_function_access_keys_configured", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled", - "cosmosdb_account_firewall_use_selected_networks", - "cosmosdb_account_use_private_endpoints", - "cosmosdb_account_use_aad_and_rbac", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_logging_enabled", - "storage_blob_public_access_level_is_disabled", - "storage_default_network_access_rule_is_denied", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_account_key_access_disabled", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_encryption_with_customer_managed_keys", - "storage_ensure_soft_delete_is_enabled", - "storage_ensure_file_shares_soft_delete_is_enabled", - "iam_role_user_access_admin_restricted", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled" - ] + "Checks": [] }, { "Id": "CCC.Vector.CN04.AR01", - "Description": "When ingestion exceeds pre-defined thresholds, the service MUST\nthrottle or reject excess vector write operations.", + "Description": "When ingestion exceeds pre-defined thresholds, the service MUST throttle or reject excess vector write operations.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN04 Enforce Ingestion Quotas and Throttling", "SubSection": "", - "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by\nrate-limiting vector submissions and enforcing quotas.", + "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by rate-limiting vector submissions and enforcing quotas.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5541,14 +3544,14 @@ }, { "Id": "CCC.Vector.CN05.AR01", - "Description": "When a rollback is attempted, the system MUST log\nthe action and verify rollback authorization.", + "Description": "When a rollback is attempted, the system MUST log the action and verify rollback authorization.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN05 Enforce Index Versioning with Rollback Protection", "SubSection": "", - "SubSectionObjective": "Ensure vector indexes are versioned and that rollback\noperations are authorized and auditable.", + "SubSectionObjective": "Ensure vector indexes are versioned and that rollback operations are authorized and auditable.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5578,14 +3581,14 @@ }, { "Id": "CCC.Vector.CN06.AR01", - "Description": "When an embedding is submitted, the service MUST validate\nthat its format and dimensionality match allowed profiles.", + "Description": "When an embedding is submitted, the service MUST validate that its format and dimensionality match allowed profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN06 Enforce Dimensional and Format Constraints", "SubSection": "", - "SubSectionObjective": "Reject embeddings that do not conform to expected model\nspecifications (dimensions, format, etc).", + "SubSectionObjective": "Reject embeddings that do not conform to expected model specifications (dimensions, format, etc).", "Applicability": [ "tlp-clear", "tlp-green", @@ -5616,14 +3619,14 @@ }, { "Id": "CCC.Vector.CN07.AR01", - "Description": "When a search request is issued, clients MUST be allowed\nto declare their requirement for exact vs approximate results.", + "Description": "When a search request is issued, clients MUST be allowed to declare their requirement for exact vs approximate results.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN07 Support Explicit ANN vs. Exact Search Configuration", "SubSection": "", - "SubSectionObjective": "Provide clients with the option to enforce exact-match\n(non-ANN) search where search fidelity is critical.", + "SubSectionObjective": "Provide clients with the option to enforce exact-match (non-ANN) search where search fidelity is critical.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5644,20 +3647,20 @@ }, { "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic\nMUST include a TLS handshake AND be encrypted using TLS 1.3 or\nhigher.", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not\nalready set, ensure that your services are configured or updated\naccordingly.\n", + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5707,21 +3710,21 @@ }, { "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST\ninclude a SSH handshake AND be encrypted using SSHv2 or higher.", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly\nimplemented SSH server with SSHv2 enabled and configured with\nstrong ciphers.\n", + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5773,20 +3776,20 @@ }, { "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, \nthen it MUST either block the request or automatically\nredirect it to the secure equivalent.", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Review firewall, load balancer, and application configurations to\nensure insecure protocols such as HTTP, FTP, and Telnet are not\nexposed. Where possible, implement automatic redirection to secure\nprotocols such as HTTPS, SFTP, SSH, and regularly scan for\nprotocol drift.\n", + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5837,21 +3840,21 @@ }, { "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol\nand service officially assigned to that port number by the IANA\nService Name and Transport Protocol Port Number Registry, and no\nother, is run on that port.", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number\nRegistry for more information about correct protocol-to-port\nassignments. Avoid running non-standard services on well-known\nports.\n", + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5904,19 +3907,19 @@ }, { "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be\nimplemented to require both client and server certificate\nauthentication for all connections.", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit\nsensitive data. Ensure both client and server certificates are\nvalidated and managed securely. Regularly review certificate\nauthorities and automate certificate rotation where possible.\n", + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5965,21 +3968,21 @@ }, { "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST only use valid, unexpired certificates issued by\na trusted certificate authority.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6003,18 +4006,18 @@ }, { "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 180 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6037,18 +4040,18 @@ }, { "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 90 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6068,21 +4071,21 @@ }, { "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST\nbe included in a list of explicitly trusted or approved locations\nwithin the trust perimeter.", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate the service's deployment\nlocation is included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6137,21 +4140,21 @@ }, { "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability\nzone MUST be included in a list of explicitly trusted or approved\nlocations within the trust perimeter.", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate that child resources can only\nbe deployed to locations included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6194,20 +4197,20 @@ }, { "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete\nand recoverable duplicate that is stored in a physically separate\ndata center.", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement automated data replication processes to ensure that\ndata is consistently duplicated in another region or availability\nzone. Regularly test data recovery from the replicated location to\nensure integrity and availability.\n", + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6249,14 +4252,14 @@ }, { "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST\nbe able to accurately represent the replication locations,\nreplication status, and data synchronization status.", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6302,14 +4305,14 @@ }, { "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource\nlogs MUST NOT be accessible from the resource they record access\nto.", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", @@ -6360,21 +4363,21 @@ }, { "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service\nor its child resources MUST NOT be possible without also disabling\nthe corresponding resource.", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging mechanisms are tightly integrated with\nservice operations, so that logging cannot be disabled without\nstopping the service itself.\n", + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6420,19 +4423,19 @@ }, { "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for\nthe service or its child resources MUST NOT be possible without\nhalting operation of the corresponding resource and publishing\ncorresponding events to monitored channels.", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should result in the redirection of\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging configurations are immutable during\nservice operation so that any changes require stopping the service\nand publishing corresponding events to monitored channels.\n", + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6479,14 +4482,14 @@ }, { "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication\nonly occurs to destinations that are explicitly included within\nthe defined trust perimeter.", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations\nthat are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6535,14 +4538,14 @@ }, { "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest\nindustry-standard encryption methods.", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN02 Encrypt Data for Storage", "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong\nencryption algorithms.", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6596,14 +4599,14 @@ }, { "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that\nall encryption keys use the latest industry-standard cryptographic\nalgorithms.", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6664,14 +4667,14 @@ }, { "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 180 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber" ], @@ -6723,14 +4726,14 @@ }, { "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that\ncustomer-managed encryption keys (CMEKs) are used.", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "", "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6786,14 +4789,14 @@ }, { "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that\naccess to encryption keys is restricted to authorized personnel\nand services, following the principle of least privilege.", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green", @@ -6855,14 +4858,14 @@ }, { "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 365 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green" @@ -6919,14 +4922,14 @@ }, { "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 90 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-red" ], @@ -6976,21 +4979,21 @@ }, { "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the\nstorage mechanism MUST NOT allow modification or deletion\nwithin 30 days of creation.", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup\nretention policies that enforce a minimum retention period of 30\ndays.\n", + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7009,20 +5012,20 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n30 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 30 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7041,18 +5044,18 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n14 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-red" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 14 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7071,14 +5074,14 @@ }, { "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user\ninterface, the authentication process MUST require multiple\nidentifying factors for authentication.", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7139,14 +5142,14 @@ }, { "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API\nendpoint, the authentication process MUST require a credential\nsuch as an API key or token AND originate from within the trust\nperimeter.", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7206,14 +5209,14 @@ }, { "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through\na user interface, the authentication process MUST require multiple\nidentifying factors from the user.", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7271,14 +5274,14 @@ }, { "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through\nan API endpoint, the authentication process MUST require a\ncredential such as an API key or token AND originate from within\nthe trust perimeter.", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7336,14 +5339,14 @@ }, { "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child\nresource, the service MUST block requests from unauthorized\nentities.", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7420,14 +5423,14 @@ }, { "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST refuse requests\nfrom unauthorized entities.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7512,14 +5515,14 @@ }, { "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource in a multi-tenant environment, the\nservice MUST refuse requests across tenant boundaries unless the\norigin is explicitly included in a pre-approved allowlist.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7594,14 +5597,14 @@ }, { "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "", "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7688,14 +5691,14 @@ }, { "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter,\nthe service MUST NOT provide any response that may indicate the\nservice exists.", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-red" ], @@ -7774,14 +5777,14 @@ }, { "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-green", "tlp-amber", @@ -7850,14 +5853,14 @@ }, { "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST log the client\nidentity, time, and result of the attempt.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7916,14 +5919,14 @@ }, { "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7972,14 +5975,14 @@ }, { "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-red" ], @@ -8028,19 +6031,19 @@ }, { "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish\nan event to a monitored channel which includes the client\nidentity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns\nindicative of enumeration activities, such as repeated access\nattempts, requests, or liveness probes. Configure alerts to notify\nsecurity teams of any activities that merit further investigation.\n", + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -8078,21 +6081,21 @@ }, { "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the\nclient identity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration\nactivities, including client identity, timestamps, and activity\nnature. Retain logs according to organizational policies, and\noccasionally review them for patterns that may indicate\nreconnaissance activities.\n", + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", diff --git a/prowler/compliance/gcp/ccc_gcp.json b/prowler/compliance/gcp/ccc_gcp.json index 5c780dcbee..e3060646c5 100644 --- a/prowler/compliance/gcp/ccc_gcp.json +++ b/prowler/compliance/gcp/ccc_gcp.json @@ -6,19 +6,19 @@ "Description": "Common Cloud Controls Catalog (CCC) for GCP", "Requirements": [ { - "Id": "CCC.AuditLog.C01.TR01", + "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature.\n", + "Recommendation": "Ensure hash of data is included in digital signature. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -52,19 +52,19 @@ ] }, { - "Id": "CCC.AuditLog.C01.TR02", + "Id": "CCC.AuditLog.CN01.AR02", "Description": "When the signature validation process is performed, then it MUST detect any missing (deleted) log file.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C01 Implement Digital Signatures With Hash Chaining", + "Section": "CCC.AuditLog.CN01 Implement Digital Signatures With Hash Chaining", "SubSection": "", - "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and\nhash chaining allows for deleted log files to be detected.", + "SubSectionObjective": "Digital signatures allows for external verification of log data tampering and hash chaining allows for deleted log files to be detected.", "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function.\n", + "Recommendation": "Ensure verification process includes a chained hash function. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -97,15 +97,15 @@ ] }, { - "Id": "CCC.AuditLog.C02.TR01", - "Description": "When a manual action is performed to generate each audit log type,\nthen the corresponding audit log type MUST be generated and recorded.", + "Id": "CCC.AuditLog.CN02.AR01", + "Description": "When a manual action is performed to generate each audit log type, then the corresponding audit log type MUST be generated and recorded.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C02 Enable And Validate All Audit Log Types", + "Section": "CCC.AuditLog.CN02 Enable And Validate All Audit Log Types", "SubSection": "", - "SubSectionObjective": "Review audit log configuration and ensure that all audit log types\nare being generated and replicated to configured sinks", + "SubSectionObjective": "Review audit log configuration and ensure that all audit log types are being generated and replicated to configured sinks", "Applicability": [ "tlp-red", "tlp-amber" @@ -151,20 +151,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR01", + "Id": "CCC.AuditLog.CN03.AR01", "Description": "When an attempt is made to disable a log source, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -205,20 +205,20 @@ ] }, { - "Id": "CCC.AuditLog.C03.TR02", - "Description": "When an attempt is made to alter the retention or object lock status\nof an external data log source or bucket, then an alert MUST be generated.", + "Id": "CCC.AuditLog.CN03.AR02", + "Description": "When an attempt is made to alter the retention or object lock status of an external data log source or bucket, then an alert MUST be generated.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C03 Alert On Audit Log Changes And Access", + "Section": "CCC.AuditLog.CN03 Alert On Audit Log Changes And Access", "SubSection": "", - "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in\naudit log configuration such as disabling exporting of logs.\nAlerts MUST also be created to detect changes in retention/object lock policies\nfor exported data log sources/buckets.", + "SubSectionObjective": "Ensure that specific alerts have been configured to detect changes in audit log configuration such as disabling exporting of logs. Alerts MUST also be created to detect changes in retention/object lock policies for exported data log sources/buckets.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured\n", + "Recommendation": "Ensure alerting is correctly configured ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -251,20 +251,20 @@ ] }, { - "Id": "CCC.AuditLog.C04.TR01", - "Description": "When audit log buckets are created then verify that server access\nlogging MUST be enabled for the audit log bucket,\nwith logs delivered to a separate, secure logging bucket.", + "Id": "CCC.AuditLog.CN04.AR01", + "Description": "When audit log buckets are created then verify that server access logging MUST be enabled for the audit log bucket, with logs delivered to a separate, secure logging bucket.", "Attributes": [ { "FamilyName": "Integrity", "FamilyDescription": "Controls designed to protected the integrity of Audit Log data.", - "Section": "CCC.AuditLog.C04 Ensure Access Logging Is Enabled on the Audit Log Bucket", + "Section": "CCC.AuditLog.CN04 Ensure Access Logging Is Enabled on the Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to\ncapture all requests made to the bucket, providing an audit trail of data access.", + "SubSectionObjective": "Ensure that access logging is enabled for the audit log storage bucket to capture all requests made to the bucket, providing an audit trail of data access.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging.\nEnsure the target logging bucket is configured for appropriate security,\nincluding restricted access and immutability.\n", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -299,20 +299,20 @@ ] }, { - "Id": "CCC.AuditLog.C05.TR01", + "Id": "CCC.AuditLog.CN05.AR01", "Description": "When audit logs are exported, then audit logs MUST be present in the configured data location.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C05 Export Audit Logs To Bucket", + "Section": "CCC.AuditLog.CN05 Export Audit Logs To Bucket", "SubSection": "", - "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated\nand can be subject to greater access control and data retention polices.", + "SubSectionObjective": "Configure audit logs to be sent to a external bucket where they can be globally replicated and can be subject to greater access control and data retention polices.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting.\n", + "Recommendation": "Configure audit log exporting. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -348,21 +348,21 @@ ] }, { - "Id": "CCC.AuditLog.C06.TR01", - "Description": "When the retention policy is applied, then data MUST\nbe automatically deleted after the configured number of days.", + "Id": "CCC.AuditLog.CN06.AR01", + "Description": "When the retention policy is applied, then data MUST be automatically deleted after the configured number of days.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C06 Enforce Retention Policy on Audit Log Bucket", + "Section": "CCC.AuditLog.CN06 Enforce Retention Policy on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are\nretained for the correct number of days as defined by your organization's policy.", + "SubSectionObjective": "Configure a custom retention policy on the designated audit log bucket to ensure that logs are retained for the correct number of days as defined by your organization's policy.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce\nthe required data retention period.\n", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -395,21 +395,21 @@ ] }, { - "Id": "CCC.AuditLog.C07.TR01", - "Description": "When a standard file deletion is attempted on an object within\nthe audit log bucket, then it MUST be prevented unless MFA is provided.", + "Id": "CCC.AuditLog.CN07.AR01", + "Description": "When a standard file deletion is attempted on an object within the audit log bucket, then it MUST be prevented unless MFA is provided.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C07 Enforce MFA Delete on Audit Log Bucket", + "Section": "CCC.AuditLog.CN07 Enforce MFA Delete on Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to\nprovide greater protection against accidental or malicious deletion of audit data.", + "SubSectionObjective": "Enable Multi-Factor Authentication (MFA) delete on the audit log bucket to provide greater protection against accidental or malicious deletion of audit data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations)\non the audit log bucket.\n", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -443,20 +443,20 @@ ] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to delete data before the object\nlock period expires, then the deletion MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to delete data before the object lock period expires, then the deletion MUST be denied.", "Attributes": [ { "FamilyName": "Availability", "FamilyDescription": "Controls designed to protected the availability of Audit Log data.", - "Section": "CCC.AuditLog.C08 Enable Object Lock On Audit Log Bucket", + "Section": "CCC.AuditLog.CN08 Enable Object Lock On Audit Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket.\nThe lock time MUST be configured to meet your organization, legal and compliance goals.\nDeletion attempts before the lock period MUST be denied.", + "SubSectionObjective": "Ensure that object log is enabled globally on all objects with the bucket. The lock time MUST be configured to meet your organization, legal and compliance goals. Deletion attempts before the lock period MUST be denied.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -487,20 +487,20 @@ ] }, { - "Id": "CCC.AuditLog.C09.TR01", + "Id": "CCC.AuditLog.CN09.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C09 Restrict Field And Log Type Access", + "Section": "CCC.AuditLog.CN09 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to audit logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data.\n", + "Recommendation": "Review field level access controls on audit data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -533,21 +533,21 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.C10.TR01", - "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny\npublic read and write access.", + "Id": "CCC.AuditLog.CN10.AR01", + "Description": "When audit log storage bucket's are created then, bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -580,21 +580,21 @@ ] }, { - "Id": "CCC.AuditLog.C10.TR02", - "Description": "When the URL of a audit log storage bucket's object is accessed publicly then,\nit should be denied by bucket policy.", + "Id": "CCC.AuditLog.CN10.AR02", + "Description": "When the URL of a audit log storage bucket's object is accessed publicly then, it should be denied by bucket policy.", "Attributes": [ { "FamilyName": "Confidentiality", "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.C10 Ensure Audit Bucket is Not Publicly Accessible", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent\nunauthorized exposure of sensitive log data.", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -626,7 +626,7 @@ ] }, { - "Id": "CCC.Build.C01.TR01", + "Id": "CCC.Build.CN01.AR01", "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", "Attributes": [ { @@ -634,7 +634,7 @@ "FamilyDescription": "TODO: Describe this control family", "Section": "", "SubSection": "CCC.Build.C01 Restrict Allowed Build Agents", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain\ncontrol over the build environment and prevent unauthorized code execution.", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", "Applicability": [ "tlp-red", "tlp-amber" @@ -668,15 +668,15 @@ "Checks": [] }, { - "Id": "CCC.Build.C02.TR01", - "Description": "Attempt to trigger a build from an unauthorized external service or\nrepository and verify that the build does not start.", + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", "Attributes": [ { "FamilyName": "Access Control", "FamilyDescription": "TODO: Describe this control family", "Section": "", "SubSection": "CCC.Build.C02 Restrict Allowed External Services for Build Triggers", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or\nrepositories to prevent unauthorized code execution or tampering.", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", "Applicability": [ "tlp-red", "tlp-amber" @@ -710,7 +710,7 @@ "Checks": [] }, { - "Id": "CCC.Build.C03.TR01", + "Id": "CCC.Build.CN03.AR01", "Description": "Attempt to access the build environment from an external network and verify that access is denied.", "Attributes": [ { @@ -718,7 +718,7 @@ "FamilyDescription": "TODO: Describe this control family", "Section": "", "SubSection": "CCC.Build.C03 Deny External Network Access for Build Environments", - "SubSectionObjective": "Ensure that build environments do not have external network access to\nprevent unauthorized external access and data exfiltration.", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", "Applicability": [ "tlp-red", "tlp-amber" @@ -760,15 +760,15 @@ ] }, { - "Id": "CCC.CntrReg.C01.TR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry\nand observe if it is flagged or rejected by the vulnerability scanning process.", + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", "Attributes": [ { "FamilyName": "Risk Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C01 Implement Vulnerability Scanning for Artifacts", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for\nvulnerabilities to identify and remediate security issues before deployment.", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", "Applicability": [ "tlp-red", "tlp-amber" @@ -805,56 +805,15 @@ ] }, { - "Id": "CCC.CntrReg.C02.TR01", - "Description": "Confirm that artifacts older than the specified retention period are automatically\ndeleted from the registry.", - "Attributes": [ - { - "FamilyName": "Data Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.C02 Implement Cleanup Policies for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to\nmanage storage effectively and reduce security risks associated with outdated versions.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-12" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.DataWar.C01.TR01", - "Description": "Attempt to access underlying database tables directly without\nusing managed views and verify that access is denied.", + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C01 Enforce Use of Managed Views for Data Access", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users\nfrom accessing underlying tables directly and enforcing consistent security policies.", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", "Applicability": [ "tlp-red", "tlp-amber" @@ -888,15 +847,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C02.TR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and\nverify that access is denied or data is masked.", + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C02 Enforce Column-Level Security Policies", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles,\npreventing unauthorized access to sensitive information.", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", "Applicability": [ "tlp-red", "tlp-amber" @@ -930,15 +889,15 @@ "Checks": [] }, { - "Id": "CCC.DataWar.C03.TR01", - "Description": "Attempt to query data rows that the user should not have access to and verify\nthat access is denied or data is not returned.", + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.C03 Enforce Row-Level Security Policies", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes,\npreventing unauthorized access to specific subsets of data.", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", "Applicability": [ "tlp-red", "tlp-amber" @@ -995,730 +954,13 @@ ] }, { - "Id": "CCC.GenAI.C01.TR01", - "Description": "Untrusted input such as user queries, RAG data or tool output\nMUST be validated before it is passed to a GenAI model.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C01.TR02", - "Description": "If malicious patterns such as prompt injection or sensitive\ndata are detected during input validation, the input MUST\nbe blocked or sanitised.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C01 Model Input Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI\nmodel in order to filter or sanitise adversarial queries\nand prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Input Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0021", - "AML.M0015" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C02.TR01", - "Description": "GenAI model output MUST be validated for format conformance,\nmalicious patterns, sensitive data and inapropriate content\nbefore being passed to users, application or plugins.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C02.TR02", - "Description": "In the event of policy violations, the AI-generated content MUST\nbe redacted, encoded or rejected.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C02 Model Output Filtering and Sanitisation", - "SubSection": "", - "SubSectionObjective": "Inspect and validate GenAI model output before passing it to\nusers, applications or plugins in order to filter or sanitise\ninsecure or unreliable output and prevent sensitive data leakage.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH03", - "CCC.GenAI.TH04", - "CCC.GenAI.TH05", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-003", - "AIR-PREV-017", - "AIR-PREV-002", - "AIR-DET-001" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Output Validation and Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0020", - "AML.M0002" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C03.TR01", - "Description": "When data is designated for model training or RAG ingestion, then its\nsource MUST be explicitly approved and its provenance documented.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [ - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_dataset_public_access", - "bigquery_table_cmk_encryption", - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "cloudstorage_bucket_log_retention_policy_lock", - "kms_key_not_publicly_accessible", - "logging_sink_created", - "iam_audit_logs_enabled", - "iam_cloud_asset_inventory_enabled", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" - ] - }, - { - "Id": "CCC.GenAI.C03.TR02", - "Description": "Data from unvetted sources MUST NOT be used in production systems.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C03 Data Provenance and Source Vetting", - "SubSection": "", - "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes\nfrom trusted, approved sources and is authorised for the\nintended purposes in order to prevent the initial introduction\nof malicious content or leaked sensitive data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-006" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Management" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0025" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C04.TR01", - "Description": "When data is ingested for training, fine-tuning or conversion\nto vector embeddings, it MUST be validated for sensitive\ninformation or malicious content.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "gcr_container_scanning_enabled", - "compute_instance_public_ip", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_firewall_rdp_access_from_the_internet_allowed", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_public_access", - "bigquery_table_cmk_encryption", - "cloudsql_instance_public_access", - "kms_key_not_publicly_accessible" - ] - }, - { - "Id": "CCC.GenAI.C04.TR02", - "Description": "If sensitive data or malicious content is detected, it must\nbe rejected, redacted or flagged for manual review.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C04 Sanitisation of Ingested Data", - "SubSection": "", - "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems\nfrom extenal sources or internal knowledge bases, whether\nfor training, conversion to vector embeddings, or real-time\nretireval, in order to remove or redact poisoned or sensitive\ndata before further processing.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH02", - "CCC.GenAI.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-002" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Training Data Sanitization" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0007" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C05.TR01", - "Description": "When a RAG-enabled system generates a response containing information\nretrieved from its knowledge base, then the response MUST include a\nverifiable citation that links back to the specific source document.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", - "Section": "CCC.GenAI.C05 Citations and Source Traceability", - "SubSection": "", - "SubSectionObjective": "Require the GenAI system to provide citations or direct links\nback to the source documents used to generate a response, in\nto enhance the transparency, trustworthiness, and verifiability\nof AI-generated content.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH09", - "CCC.GenAI.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-DET-013" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "iam_cloud_asset_inventory_enabled" - ] - }, - { - "Id": "CCC.GenAI.C06.TR01", - "Description": "When an LLM invokes an external tool (e.g., an API, a plugin),\nthen the tool MUST operate with the least privileges required\nfor performing its intended functionality.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", - "Section": "CCC.GenAI.C06 Least Privilege for Plugins", - "SubSection": "", - "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system\ncan call to limit the potential damage if an agent is coerced\nto perform unintended actions or vulnerabilities in the tools\nare exploited.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH07", - "CCC.GenAI.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Agent Permissions" - ] - } - ] - } - ], - "Checks": [ - "apikeys_api_restrictions_configured", - "compute_instance_default_service_account_in_use_with_full_api_access", - "compute_instance_default_service_account_in_use", - "gke_cluster_no_default_service_account", - "iam_no_service_roles_at_project_level", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused" - ] - }, - { - "Id": "CCC.GenAI.C07.TR01", - "Description": "When an application makes an API call to a foundational model in a\nproduction environment, then it MUST specify an explicit version\nidentifier.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "The Configuration Management control family involves establishing,\nmaintaining and monitoring the configuration of the service and\nrelated applications and infrastructure to ensure consistency,\nsecure defaults and compliance.\n", - "Section": "CCC.GenAI.C07 Model Version Pinning", - "SubSection": "", - "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific,\ntested version of a foundational model to prevent unexpected\nbehaviour changes introduced by provider-side updates.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-010" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.GenAI.C08.TR01", - "Description": "When a new AI model is considered for production deployment, it\nMUST undergo a formal red teaming and quality assurance review.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "artifacts_container_analysis_enabled", - "gcr_container_scanning_enabled", - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_log_retention_policy_lock", - "iam_audit_logs_enabled", - "iam_cloud_asset_inventory_enabled", - "logging_sink_created", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "dataproc_encrypted_with_cmks_disabled" - ] - }, - { - "Id": "CCC.GenAI.C08.TR02", - "Description": "If model quality review or red teaming identifies an issue that exceeds\nthe organization's risk tolerance, the model MUST NOT be deployed until\nthe issue is remediated.", - "Attributes": [ - { - "FamilyName": "Model Assurance and Evaluation", - "FamilyDescription": "The Model Assurance and Evaluation control family encompasses\nthe proactiveand continuous processes of testing and validating\nthe AI model's behavior to ensure it aligns with safety, ethical,\nand quality standards.\n", - "Section": "CCC.GenAI.C08 Quality Control and Red Teaming", - "SubSection": "", - "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial\ntesting (red teaming) to ensure GenAI system meet all business,\nquality, security and compliance requirements before getting deployed\ninto production environments.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.GenAI.TH01", - "CCC.GenAI.TH02", - "CCC.GenAI.TH04", - "CCC.GenAI.TH08", - "CCC.GenAI.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "FINOS-AIGF", - "Identifiers": [ - "AIR-PREV-005" - ] - }, - { - "ReferenceId": "SAIF", - "Identifiers": [ - "Adversarial Training and Testing", - "Red Teaming", - "Product Governance" - ] - }, - { - "ReferenceId": "MITRE-ATLAS", - "Identifiers": [ - "AML.M0008" - ] - } - ] - } - ], - "Checks": [ - "dataproc_encrypted_with_cmks_disabled", - "gcr_container_scanning_enabled", - "artifacts_container_analysis_enabled", - "iam_audit_logs_enabled", - "iam_cloud_asset_inventory_enabled", - "logging_sink_created", - "kms_key_not_publicly_accessible", - "bigquery_dataset_cmk_encryption", - "bigquery_dataset_public_access" - ] - }, - { - "Id": "CCC.KeyMgmt.C01.TR01", - "Description": "When a key version is scheduled for deletion or disabled, an\nalert MUST be generated within five minutes.", + "Id": "CCC.KeyMgmt.CN01.AR01", + "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", "Attributes": [ { "FamilyName": "Logging and Metrics Publication", "FamilyDescription": "Controls that collect, alert, and retain key-management events.", - "Section": "CCC.KeyMgmt.C01 Alert on Key-version Changes", + "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", "SubSection": "", "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", "Applicability": [ @@ -1753,13 +995,13 @@ "Checks": [] }, { - "Id": "CCC.KeyMgmt.C02.TR01", - "Description": "When IAM roles and key policies are reviewed, Decrypt permission\nMUST be granted exclusively to documented authorised principals.", + "Id": "CCC.KeyMgmt.CN02.AR01", + "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", - "Section": "CCC.KeyMgmt.C02 Limit Decrypt Permissions", + "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", "SubSection": "", "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", "Applicability": [ @@ -1796,13 +1038,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C03.TR01", - "Description": "When rotation settings are examined, rotation MUST be enabled with\nan interval not exceeding 365 days.", + "Id": "CCC.KeyMgmt.CN03.AR01", + "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C03 Enforce Automatic Rotation", + "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", "SubSection": "", "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", "Applicability": [ @@ -1838,13 +1080,13 @@ ] }, { - "Id": "CCC.KeyMgmt.C04.TR01", - "Description": "When a key import request is processed, the key MUST use an\napproved algorithm (RSA-2048+, EC-P256+) and originate from a\ncertified HSM.", + "Id": "CCC.KeyMgmt.CN04.AR01", + "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", "Attributes": [ { "FamilyName": "Key Lifecycle Management", "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.C04 Validate Imported Keys", + "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", "SubSection": "", "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", "Applicability": [ @@ -1881,13 +1123,13 @@ ] }, { - "Id": "CCC.LB.C01.TR01", - "Description": "When a single client sends more than 2000 requests within any\n5-minute sliding window, the load balancer MUST throttle all\nsubsequent requests from that client for at least 60 seconds.", + "Id": "CCC.LB.CN01.AR01", + "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1895,7 +1137,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via\nsynthetic traffic tests.\n", + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1936,13 +1178,13 @@ ] }, { - "Id": "CCC.LB.C01.TR02", - "Description": "When throttling is invoked, the load balancer MUST\nrecord the event in the access log within 5 minutes\nfor alerting and trend analysis.", + "Id": "CCC.LB.CN01.AR02", + "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C01 Enforce and Detect Rate Limiting", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", "Applicability": [ @@ -1950,7 +1192,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Enable access logging and configure metric filters\non HTTP 429 counts to trigger alerts.\n", + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1986,21 +1228,21 @@ ] }, { - "Id": "CCC.LB.C06.TR01", - "Description": "When more than 10 percent of targets change from healthy to\nunhealthy within five minutes, an alert MUST be issued.", + "Id": "CCC.LB.CN06.AR01", + "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.\n", - "Section": "CCC.LB.C06 Secure Health-Check Telemetry", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", "SubSection": "", - "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on\nabnormal status changes.", + "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Instrument metrics for health check results and target\nremoval events. Configure monitoring alarms to alert\non abnormal spikes in unhealthy targets.\n", + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2032,21 +1274,21 @@ ] }, { - "Id": "CCC.LB.C04.TR01", - "Description": "When routing weights change, the request MUST originate\nfrom an explicitly defined and trusted identity and MUST\nbe logged.", + "Id": "CCC.LB.CN04.AR01", + "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C04 Enforce Distribution Policies", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN04 Enforce Distribution Policies", "SubSection": "", - "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified\nonly by trusted identities.", + "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Define a list of trusted principals allowed to modify\nrouting configurations. Enforce via conditional access\npolicies, and log changes using audit logging.\n", + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -2083,15 +1325,15 @@ ] }, { - "Id": "CCC.LB.C05.TR01", - "Description": "When stickiness is enabled, session cookies MUST expire\nwithin 30 minutes of inactivity.", + "Id": "CCC.LB.CN05.AR01", + "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C05 Validate Session Affinity", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN05 Validate Session Affinity", "SubSection": "", - "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking\nrisks.", + "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2139,15 +1381,15 @@ ] }, { - "Id": "CCC.LB.C09.TR01", - "Description": "When an API call originates outside the approved CIDR\nset, the request MUST be denied.", + "Id": "CCC.LB.CN09.AR01", + "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.\n", - "Section": "CCC.LB.C09 Restrict Management API Access", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "Section": "CCC.LB.CN09 Restrict Management API Access", "SubSection": "", - "SubSectionObjective": "Limit load-balancer API calls to authorised identities and\ntrusted networks.", + "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2194,15 +1436,15 @@ ] }, { - "Id": "CCC.LB.C02.TR01", - "Description": "When concurrent connections reach 80 percent of capacity, the\nautoscaling group MUST add at least one instance within five\nminutes.", + "Id": "CCC.LB.CN02.AR01", + "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C02 Auto-Scale Load Balancer Capacity", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", "SubSection": "", - "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic\nspikes.", + "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2236,15 +1478,15 @@ "Checks": [] }, { - "Id": "CCC.LB.C07.TR01", - "Description": "When responses pass through the load balancer, the\n\"Server\" header MUST be replaced with \"lb\".", + "Id": "CCC.LB.CN07.AR01", + "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of\ntraffic processed by the load balancer.\n", - "Section": "CCC.LB.C07 Scrub Sensitive Headers", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "Section": "CCC.LB.CN07 Scrub Sensitive Headers", "SubSection": "", - "SubSectionObjective": "Remove headers that disclose internal details or software\nversions from HTTP responses.", + "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2278,15 +1520,15 @@ "Checks": [] }, { - "Id": "CCC.LB.C08.TR01", - "Description": "When a certificate is within 30 days of expiry, automated renewal\nMUST complete and deploy a new certificate within 24 hours.", + "Id": "CCC.LB.CN08.AR01", + "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", "Attributes": [ { "FamilyName": "Encryption", "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", - "Section": "CCC.LB.C08 Automate Certificate Renewal", + "Section": "CCC.LB.CN08 Automate Certificate Renewal", "SubSection": "", - "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and\ndeployment before expiry.", + "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2320,15 +1562,15 @@ "Checks": [] }, { - "Id": "CCC.Logging.C01.TR01", - "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be\nenabled by default and directed to the central sink.", + "Id": "CCC.Logging.CN01.AR01", + "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2368,15 +1610,15 @@ ] }, { - "Id": "CCC.Logging.C01.TR02", - "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant\nlogs (e.g., OS, application, service logs) to the central log sink.", + "Id": "CCC.Logging.CN01.AR02", + "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C01 Centralized and Comprehensive Log Aggregation", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including\napplications, operating systems, network traffic, and cloud service activity, are captured\nautomatically and streamed to a central, secure log management service.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2419,15 +1661,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR01", - "Description": "When a new log bucket or stream is created, its retention policy MUST be configured\nin accordance with organisation's data retention policy.", + "Id": "CCC.Logging.CN02.AR01", + "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2464,15 +1706,15 @@ ] }, { - "Id": "CCC.Logging.C02.TR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined\nin the organisation's data retention policy, it MUST return an empty result.", + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C02 Enforce Data Retention Policy for Logs", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's\ndata retention policy.", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2509,20 +1751,20 @@ ] }, { - "Id": "CCC.AuditLog.C08.TR01", - "Description": "When an attempt is made to modify or delete data before the object\nlock period expires, then the action MUST be denied.", + "Id": "CCC.AuditLog.CN08.AR01", + "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.\n", - "Section": "CCC.Logging.C03 Enable Object Lock On Log Bucket", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", "SubSection": "", - "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection\nusing object lock on log storage buckets. This prevents logs from being modified\nor deleted during the defined retention period, supporting compliance and forensic\nintegrity.", + "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy.\n", + "Recommendation": "Configure object lock policy. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2553,20 +1795,20 @@ ] }, { - "Id": "CCC.AuditLog.C04.TR01", + "Id": "CCC.AuditLog.CN04.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C04 Restrict Field And Log Type Access", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", "SubSection": "", - "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically\npossible limit the log fields users have access to to prevent accidental exposure to sensitive\ninformation such as PII.", + "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on log data.\n", + "Recommendation": "Review field level access controls on log data. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2599,21 +1841,21 @@ "Checks": [] }, { - "Id": "CCC.Logging.C05.TR01", - "Description": "When a log storage bucket is created, the bucket's access control settings MUST\nexplicitly deny public read and write access.", + "Id": "CCC.Logging.CN05.AR01", + "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2647,21 +1889,21 @@ ] }, { - "Id": "CCC.Logging.C05.TR02", - "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied\nby bucket policy.", + "Id": "CCC.Logging.CN05.AR02", + "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs.\n", - "Section": "CCC.Logging.C05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized\naccess to sensitive log data. In addition, logs should be replicated to another cloud\nregion to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access.\nRegularly review bucket permissions to ensure no public access has been inadvertently granted.\n", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -2693,15 +1935,15 @@ ] }, { - "Id": "CCC.Logging.C06.TR01", - "Description": "When a single principal executes an anomalously high number of log queries,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN06.AR01", + "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C06 Detect and Alert on Potential Log Exfiltration", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", "SubSection": "", - "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt\nto exfiltrate log data.", + "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", "Applicability": [ "tlp-green", "tlp-amber", @@ -2748,15 +1990,15 @@ ] }, { - "Id": "CCC.Logging.C07.TR01", - "Description": "When an audit log event is recorded that corresponds to a modification of the logging service\nconfiguration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule,\nan alert MUST be generated.", + "Id": "CCC.Logging.CN07.AR01", + "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events.\n", - "Section": "CCC.Logging.C07 Detect and Alert on Log Service Tampering", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", "SubSection": "", - "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified,\nor deleted, indicating a defense evasion attempt.", + "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", "Applicability": [ "tlp-clear", "tlp-green", @@ -2803,13 +2045,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR01", + "Id": "CCC.ObjStor.CN01.AR01", "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2861,13 +2103,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR02", + "Id": "CCC.ObjStor.CN01.AR02", "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2916,13 +2158,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C01.TR03", + "Id": "CCC.ObjStor.CN01.AR03", "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -2979,13 +2221,13 @@ ] }, { - "Id": "CCC.ObjStor.C01.TR04", + "Id": "CCC.ObjStor.CN01.AR04", "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C01 Prevent Unencrypted Requests", + "Section": "CCC.CN01 Prevent Unencrypted Requests", "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", "Applicability": [ @@ -3043,13 +2285,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR01", + "Id": "CCC.ObjStor.CN03.AR01", "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3101,13 +2343,13 @@ ] }, { - "Id": "CCC.ObjStor.C03.TR02", + "Id": "CCC.ObjStor.CN03.AR02", "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C03 Implement Multi-factor Authentication (MFA) for Access", + "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", "Applicability": [ @@ -3159,13 +2401,13 @@ ] }, { - "Id": "CCC.ObjStor.C04.TR01", + "Id": "CCC.ObjStor.CN04.AR01", "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3217,13 +2459,13 @@ ] }, { - "Id": "CCC.ObjStor.C04.TR02", + "Id": "CCC.ObjStor.CN04.AR02", "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C04 Log All Access and Changes", + "Section": "CCC.CN04 Log All Access and Changes", "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", "Applicability": [ @@ -3275,13 +2517,13 @@ ] }, { - "Id": "CCC.ObjStor.C05.TR01", + "Id": "CCC.ObjStor.CN05.AR01", "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3331,13 +2573,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C05.TR02", + "Id": "CCC.ObjStor.CN05.AR02", "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3387,13 +2629,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C05.TR03", + "Id": "CCC.ObjStor.CN05.AR03", "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3443,13 +2685,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C05.TR04", + "Id": "CCC.ObjStor.CN05.AR04", "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C05 Prevent Access from Untrusted Entities", + "Section": "CCC.CN05 Prevent Access from Untrusted Entities", "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", "Applicability": [ @@ -3499,13 +2741,13 @@ "Checks": [] }, { - "Id": "CCC.ObjStor.C06.TR01", + "Id": "CCC.ObjStor.CN06.AR01", "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", "Attributes": [ { "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C06 Prevent Deployment in Restricted Regions", + "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", "Applicability": [ @@ -3558,13 +2800,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR01", + "Id": "CCC.ObjStor.CN02.AR01", "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3616,13 +2858,13 @@ ] }, { - "Id": "CCC.ObjStor.C02.TR02", + "Id": "CCC.ObjStor.CN02.AR02", "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.C02 Ensure Data Encryption at Rest for All Stored Data", + "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ @@ -3673,982 +2915,9 @@ "cloudstorage_bucket_uniform_bucket_level_access" ] }, - { - "Id": "CCC.MLDE.CN01.AR01", - "Description": "Verify that only authorized users can access MLDE resources,\nand that access modes are properly defined and enforced.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", - "SubSection": "", - "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE)\nresources is strictly defined and controlled.\nOnly authorized users with appropriate permissions can access these environments,\nmitigating the risk of unauthorized access, data leakage, or service disruption.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.1", - "2013 A.9.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-2", - "AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-01", - "IAM-02" - ] - } - ] - } - ], - "Checks": [ - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_no_service_roles_at_project_level", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "gke_cluster_no_default_service_account", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access" - ] - }, - { - "Id": "CCC.MLDE.CN03.AR01", - "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "compute_project_os_login_enabled", - "compute_instance_block_project_wide_ssh_keys_disabled", - "compute_firewall_ssh_access_from_the_internet_allowed" - ] - }, - { - "Id": "CCC.MLDE.CN03.AR02", - "Description": "For MLDE instances without sensitive data, ensure that root access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the\nrisk of unauthorized system modifications and potential security breaches.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08", - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_instance_public_ip", - "compute_instance_block_project_wide_ssh_keys_disabled", - "compute_project_os_login_enabled", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account", - "iam_sa_no_administrative_privileges", - "iam_no_service_roles_at_project_level", - "iam_audit_logs_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR01", - "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "compute_instance_serial_ports_in_use" - ] - }, - { - "Id": "CCC.MLDE.CN04.AR02", - "Description": "For MLDE instances without sensitive data, ensure that terminal access is only\nenabled when necessary and properly authorized.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of\nunauthorized commands and potential system compromise.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.2.3" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_serial_ports_in_use", - "compute_instance_public_ip" - ] - }, - { - "Id": "CCC.MLDE.CN02.AR01", - "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN02.AR02", - "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", - "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSI-05", - "DSI-07" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.2.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR01", - "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [ - "artifacts_container_analysis_enabled", - "gcr_container_scanning_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN05.AR02", - "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", - "Attributes": [ - { - "FamilyName": "Configuration Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", - "SubSectionObjective": "Limit the virtual machine and container image options available when creating\nnew MLDE instances to approved and secure configurations.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.5.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CM-2" - ] - } - ] - } - ], - "Checks": [ - "artifacts_container_analysis_enabled", - "gcr_container_scanning_enabled", - "compute_instance_shielded_vm_enabled", - "compute_instance_confidential_computing_enabled", - "compute_instance_public_ip", - "compute_instance_ip_forwarding_is_enabled", - "compute_instance_default_service_account_in_use", - "compute_instance_serial_ports_in_use", - "compute_instance_block_project_wide_ssh_keys_disabled", - "compute_instance_encryption_with_csek_enabled", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_firewall_rdp_access_from_the_internet_allowed" - ] - }, - { - "Id": "CCC.MLDE.CN06.AR01", - "Description": "Verify that automatic scheduled upgrades are enabled on user-managed\nMLDE instances containing sensitive data.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [ - "artifacts_container_analysis_enabled", - "gcr_container_scanning_enabled" - ] - }, - { - "Id": "CCC.MLDE.CN06.AR02", - "Description": "Ensure that the upgrade schedule is appropriately configured and\ndoes not interfere with critical operations.", - "Attributes": [ - { - "FamilyName": "Vulnerability Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", - "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the\nlatest security patches by enforcing automatic scheduled upgrades.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH04", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IP-12" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-01", - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.6.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-2" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.MLDE.CN07.AR01", - "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "compute_instance_public_ip", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed" - ] - }, - { - "Id": "CCC.MLDE.CN07.AR02", - "Description": "For MLDE instances without sensitive data requiring public access,\nensure that appropriate security controls are in place and access is approved.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH02", - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "compute_instance_public_ip", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "cloudsql_instance_public_ip", - "cloudsql_instance_private_ip_assignment", - "cloudsql_instance_public_access" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR01", - "Description": "Verify that MLDE instances containing sensitive data can only be deployed in\napproved virtual networks with appropriate security controls.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "compute_network_default_in_use", - "compute_network_not_legacy", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "cloudsql_instance_private_ip_assignment", - "cloudsql_instance_public_access" - ] - }, - { - "Id": "CCC.MLDE.CN08.AR02", - "Description": "Ensure that MLDE instances without sensitive data are deployed in\nnetworks that meet organizational security standards.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", - "SubSection": "", - "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to\nensure they are deployed within approved and secure network environments.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green", - "tlp-clear" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.MLDE.TH01", - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_network_not_legacy", - "compute_network_default_in_use", - "compute_instance_public_ip", - "compute_instance_ip_forwarding_is_enabled", - "compute_instance_shielded_vm_enabled", - "compute_instance_serial_ports_in_use", - "cloudsql_instance_private_ip_assignment", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access", - "cloudstorage_bucket_public_access" - ] - }, - { - "Id": "CCC.Message.CN01.AR01", - "Description": "Attempt to publish a message without using a customer-managed encryption key\nand verify that the message is rejected or not stored.", - "Attributes": [ - { - "FamilyName": "Encryption", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", - "SubSection": "", - "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK)\nto provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "dataproc_encrypted_with_cmks_disabled", - "compute_instance_encryption_with_csek_enabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled" - ] - }, { "Id": "CCC.Monitor.CN01.AR01", - "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then\nRate Limiting MUST be applied and an Audit Alert MUST be generated.", + "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4705,7 +2974,7 @@ }, { "Id": "CCC.Monitor.CN02.AR01", - "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied\nto reduce the network impact of traffic and an alert must triggered.", + "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", "Attributes": [ { "FamilyName": "Logging & Monitoring", @@ -4764,14 +3033,14 @@ }, { "Id": "CCC.Monitor.CN03.AR01", - "Description": "When external systems have approved access to internal systems not normally available for public access\nthen they MUST be secured to prevent unauthorised access jumping through to the internal systems and\nonly allow access to specific internal services.", + "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", "Attributes": [ { "FamilyName": "Identity and Access Management", "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN03 Access External Monitoring", "SubSection": "", - "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to\nensure they don't become an attack path, preventing monitoring systems from forging network requests to\ngain access to internal systems.", + "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4813,7 +3082,7 @@ }, { "Id": "CCC.Monitor.CN04.AR01", - "Description": "When monitoring dashboards display degraded services which may become potential targets then the\ndashboard MUST be protected from unauthorised access.", + "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4858,7 +3127,7 @@ }, { "Id": "CCC.Monitor.CN05.AR01", - "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised\nresponders silence or acknowledge the alert.", + "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", "Attributes": [ { "FamilyName": "Identity and Access Management", @@ -4920,7 +3189,7 @@ "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", "SubSection": "", - "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised\nsystem pushing fabricated metrics about a different service", + "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", "Applicability": [ "tlp-clear", "tlp-green", @@ -4964,199 +3233,16 @@ "iam_service_account_unused" ] }, - { - "Id": "CCC.SecMgmt.CN01.AR01", - "Description": "Attempt to use an outdated version of a secret after its rotation period\nhas passed and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to\nreduce the risk of secret compromise and unauthorized access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH14" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "apikeys_key_rotated_in_90_days", - "iam_sa_user_managed_key_rotate_90_days", - "kms_key_rotation_enabled" - ] - }, - { - "Id": "CCC.SecMgmt.CN02.AR01", - "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data Protection", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", - "SubSection": "", - "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per\norganizational data residency and compliance requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03", - "CCC.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.SvlsComp.CN01.AR01", - "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", - "SubSection": "", - "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint,\nallowing it to communicate securely within a virtual private network and preventing\nunauthorized external access.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-8" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access", - "cloudsql_instance_private_ip_assignment", - "cloudstorage_bucket_public_access", - "bigquery_dataset_public_access", - "compute_public_address_shodan" - ] - }, - { - "Id": "CCC.SvlsComp.CN02.AR01", - "Description": "Send requests to invoke the function up to the allowed threshold and confirm they\nare successful; then send additional requests exceeding the threshold from the same\nentity and verify that they are denied.", - "Attributes": [ - { - "FamilyName": "Availability", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", - "SubSection": "", - "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity,\npreventing resource exhaustion and denial of service attacks.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH12" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5" - ] - } - ] - } - ], - "Checks": [] - }, { "Id": "CCC.VPC.CN01.AR01", - "Description": "When a subscription is created, the subscription MUST NOT\ncontain default network resources.", + "Description": "When a subscription is created, the subscription MUST NOT contain default network resources.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN01 Restrict Default Network Creation", "SubSection": "", - "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related\nresources during subscription initialization to avoid insecure default\nconfigurations and enforce custom network policies.", + "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related resources during subscription initialization to avoid insecure default configurations and enforce custom network policies.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5202,74 +3288,16 @@ "compute_network_default_in_use" ] }, - { - "Id": "CCC.VPC.CN02.AR01", - "Description": "When a resource is created in a public subnet, that resource\nMUST NOT be assigned an external IP address by default.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", - "SubSection": "", - "SubSectionObjective": "Restrict the creation of resources in the public subnet with\ndirect access to the internet to minimize attack surfaces.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "SEF-05" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access" - ] - }, { "Id": "CCC.VPC.CN03.AR01", - "Description": "When a VPC peering connection is requested, the service MUST\nprevent connections from VPCs that are not explicitly\nallowed.", + "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN03 Restrict VPC Peering to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly\nauthorized destinations to limit network exposure and enforce boundary\ncontrols.", + "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly authorized destinations to limit network exposure and enforce boundary controls.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5316,14 +3344,14 @@ }, { "Id": "CCC.VPC.CN04.AR01", - "Description": "When any network traffic goes to or from an interface in the VPC,\nthe service MUST capture and log all relevant information.", + "Description": "When any network traffic goes to or from an interface in the VPC, the service MUST capture and log all relevant information.", "Attributes": [ { "FamilyName": "Network Security", "FamilyDescription": "TODO: Describe this control family", "Section": "CCC.VPC.CN04 Enforce VPC Flow Logs on VPCs", "SubSection": "", - "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic\ninformation.", + "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic information.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5371,14 +3399,14 @@ }, { "Id": "CCC.Vector.CN01.AR01", - "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it\nmatches expected schema, dimension, and format profiles.", + "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN01 Validate Embeddings Before Indexing", "SubSection": "", - "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated\nbefore indexing to prevent poisoning or corruption.", + "SubSectionObjective": "Ensure all incoming embeddings are structurally and statistically validated before indexing to prevent poisoning or corruption.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5410,14 +3438,14 @@ }, { "Id": "CCC.Vector.CN02.AR01", - "Description": "When an index lifecycle event is triggered, the service MUST\nverify that the actor has explicit permissions for the operation type.", + "Description": "When an index lifecycle event is triggered, the service MUST verify that the actor has explicit permissions for the operation type.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN02 Enforce Role-Based Index Lifecycle Management", "SubSection": "", - "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged\nidentities using fine-grained access controls.", + "SubSectionObjective": "Restrict index lifecycle operations (create, delete, rollback) to privileged identities using fine-grained access controls.", "Applicability": [ "tlp-clear", "tlp-green", @@ -5455,14 +3483,14 @@ }, { "Id": "CCC.Vector.CN03.AR01", - "Description": "When a metadata filter is applied to a query, the service MUST\nverify the requester is authorized to access that field.", + "Description": "When a metadata filter is applied to a query, the service MUST verify the requester is authorized to access that field.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN03 Enforce Metadata-Level Access Controls", "SubSection": "", - "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to\nprevent unauthorized exposure or inference.", + "SubSectionObjective": "Apply access control policies to metadata fields used in filtering to prevent unauthorized exposure or inference.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5489,28 +3517,18 @@ ] } ], - "Checks": [ - "iam_audit_logs_enabled", - "iam_account_access_approval_enabled", - "iam_no_service_roles_at_project_level", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "iam_cloud_asset_inventory_enabled", - "compute_instance_default_service_account_in_use" - ] + "Checks": [] }, { "Id": "CCC.Vector.CN04.AR01", - "Description": "When ingestion exceeds pre-defined thresholds, the service MUST\nthrottle or reject excess vector write operations.", + "Description": "When ingestion exceeds pre-defined thresholds, the service MUST throttle or reject excess vector write operations.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN04 Enforce Ingestion Quotas and Throttling", "SubSection": "", - "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by\nrate-limiting vector submissions and enforcing quotas.", + "SubSectionObjective": "Prevent ingestion-based DoS or index pollution by rate-limiting vector submissions and enforcing quotas.", "Applicability": [ "tlp-green", "tlp-amber", @@ -5540,14 +3558,14 @@ }, { "Id": "CCC.Vector.CN05.AR01", - "Description": "When a rollback is attempted, the system MUST log\nthe action and verify rollback authorization.", + "Description": "When a rollback is attempted, the system MUST log the action and verify rollback authorization.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN05 Enforce Index Versioning with Rollback Protection", "SubSection": "", - "SubSectionObjective": "Ensure vector indexes are versioned and that rollback\noperations are authorized and auditable.", + "SubSectionObjective": "Ensure vector indexes are versioned and that rollback operations are authorized and auditable.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5588,14 +3606,14 @@ }, { "Id": "CCC.Vector.CN06.AR01", - "Description": "When an embedding is submitted, the service MUST validate\nthat its format and dimensionality match allowed profiles.", + "Description": "When an embedding is submitted, the service MUST validate that its format and dimensionality match allowed profiles.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN06 Enforce Dimensional and Format Constraints", "SubSection": "", - "SubSectionObjective": "Reject embeddings that do not conform to expected model\nspecifications (dimensions, format, etc).", + "SubSectionObjective": "Reject embeddings that do not conform to expected model specifications (dimensions, format, etc).", "Applicability": [ "tlp-clear", "tlp-green", @@ -5626,14 +3644,14 @@ }, { "Id": "CCC.Vector.CN07.AR01", - "Description": "When a search request is issued, clients MUST be allowed\nto declare their requirement for exact vs approximate results.", + "Description": "When a search request is issued, clients MUST be allowed to declare their requirement for exact vs approximate results.", "Attributes": [ { "FamilyName": "Vector Indexing", "FamilyDescription": "Controls specific to the management and protection of vector embedding and index operations.", "Section": "CCC.Vector.CN07 Support Explicit ANN vs. Exact Search Configuration", "SubSection": "", - "SubSectionObjective": "Provide clients with the option to enforce exact-match\n(non-ANN) search where search fidelity is critical.", + "SubSectionObjective": "Provide clients with the option to enforce exact-match (non-ANN) search where search fidelity is critical.", "Applicability": [ "tlp-amber", "tlp-red" @@ -5654,20 +3672,20 @@ }, { "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic\nMUST include a TLS handshake AND be encrypted using TLS 1.3 or\nhigher.", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not\nalready set, ensure that your services are configured or updated\naccordingly.\n", + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5714,21 +3732,21 @@ }, { "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST\ninclude a SSH handshake AND be encrypted using SSHv2 or higher.", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly\nimplemented SSH server with SSHv2 enabled and configured with\nstrong ciphers.\n", + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5775,20 +3793,20 @@ }, { "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, \nthen it MUST either block the request or automatically\nredirect it to the secure equivalent.", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Review firewall, load balancer, and application configurations to\nensure insecure protocols such as HTTP, FTP, and Telnet are not\nexposed. Where possible, implement automatic redirection to secure\nprotocols such as HTTPS, SFTP, SSH, and regularly scan for\nprotocol drift.\n", + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5841,21 +3859,21 @@ }, { "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol\nand service officially assigned to that port number by the IANA\nService Name and Transport Protocol Port Number Registry, and no\nother, is run on that port.", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number\nRegistry for more information about correct protocol-to-port\nassignments. Avoid running non-standard services on well-known\nports.\n", + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5903,19 +3921,19 @@ }, { "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be\nimplemented to require both client and server certificate\nauthentication for all connections.", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect\ndata integrity and confidentiality.", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit\nsensitive data. Ensure both client and server certificates are\nvalidated and managed securely. Regularly review certificate\nauthorities and automate certificate rotation where possible.\n", + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5962,21 +3980,21 @@ }, { "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST only use valid, unexpired certificates issued by\na trusted certificate authority.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -5992,18 +4010,18 @@ }, { "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 180 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6019,18 +4037,18 @@ }, { "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption,\nthe service MUST rotate active certificates within 90 days of\nissuance.", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited\nlifetime to reduce the risk of compromise and ensure the use of\nup-to-date security practices.", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", "Applicability": [ "tlp-red" ], - "Recommendation": "Track certificate expiration dates and automate certificate\nrenewal where possible. Use certificate management tools to ensure\nonly certificates from trusted authorities are deployed.\n", + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6049,21 +4067,21 @@ }, { "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST\nbe included in a list of explicitly trusted or approved locations\nwithin the trust perimeter.", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate the service's deployment\nlocation is included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6104,21 +4122,21 @@ }, { "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability\nzone MUST be included in a list of explicitly trusted or approved\nlocations within the trust perimeter.", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on\ninfrastructure in locations that are explicitly included within a\ndefined trust perimeter.", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based\non organizational policies. Validate that child resources can only\nbe deployed to locations included in this list.\n", + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6159,20 +4177,20 @@ }, { "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete\nand recoverable duplicate that is stored in a physically separate\ndata center.", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement automated data replication processes to ensure that\ndata is consistently duplicated in another region or availability\nzone. Regularly test data recovery from the replicated location to\nensure integrity and availability.\n", + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6213,14 +4231,14 @@ }, { "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST\nbe able to accurately represent the replication locations,\nreplication status, and data synchronization status.", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to\nprotect against data loss due to hardware failures, natural disasters,\nor other catastrophic events.", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6264,14 +4282,14 @@ }, { "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource\nlogs MUST NOT be accessible from the resource they record access\nto.", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", @@ -6321,21 +4339,21 @@ }, { "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service\nor its child resources MUST NOT be possible without also disabling\nthe corresponding resource.", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging mechanisms are tightly integrated with\nservice operations, so that logging cannot be disabled without\nstopping the service itself.\n", + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6381,19 +4399,19 @@ }, { "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for\nthe service or its child resources MUST NOT be possible without\nhalting operation of the corresponding resource and publishing\ncorresponding events to monitored channels.", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location\nthat cannot be manipulated from the context of the service(s) it\ncontains logs for.", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should result in the redirection of\nlogs, as this could indicate an attempt to cover up unauthorized\naccess. Ensure that logging configurations are immutable during\nservice operation so that any changes require stopping the service\nand publishing corresponding events to monitored channels.\n", + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6435,14 +4453,14 @@ }, { "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication\nonly occurs to destinations that are explicitly included within\nthe defined trust perimeter.", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations\nthat are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6484,14 +4502,14 @@ }, { "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest\nindustry-standard encryption methods.", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN02 Encrypt Data for Storage", "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong\nencryption algorithms.", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", "Applicability": [ "tlp-green", "tlp-amber", @@ -6543,14 +4561,14 @@ }, { "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that\nall encryption keys use the latest industry-standard cryptographic\nalgorithms.", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6605,14 +4623,14 @@ }, { "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 180 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber" ], @@ -6666,14 +4684,14 @@ }, { "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that\ncustomer-managed encryption keys (CMEKs) are used.", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "", "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-amber", "tlp-red" @@ -6728,14 +4746,14 @@ }, { "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that\naccess to encryption keys is restricted to authorized personnel\nand services, following the principle of least privilege.", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green", @@ -6792,14 +4810,14 @@ }, { "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 365 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-clear", "tlp-green" @@ -6854,14 +4872,14 @@ }, { "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys\nwithin 90 days of issuance.", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN11 Protect Encryption Keys", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing\nthe use of approved algorithms, regular key rotation, and\ncustomer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", "Applicability": [ "tlp-red" ], @@ -6915,21 +4933,21 @@ }, { "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the\nstorage mechanism MUST NOT allow modification or deletion\nwithin 30 days of creation.", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup\nretention policies that enforce a minimum retention period of 30\ndays.\n", + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6948,20 +4966,20 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n30 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 30 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -6979,18 +4997,18 @@ }, { "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the\nmost recent backup MUST have a creation date within the past\n14 days.", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity,\navailability, and sovereignty of data across its lifecycle.\nThese controls govern how data is transmitted, stored,\nreplicated, and protected from unauthorized access, tampering,\nor exposure beyond defined trust perimeters.\n", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", "Section": "CCC.Core.CN14 Maintain Recent Backups", "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and\nsubject to a retention policy that limits deletion.", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", "Applicability": [ "tlp-red" ], - "Recommendation": "Implement automated backup processes to ensure that backups are\ncreated regularly. Monitor backup schedules and verify that the\nmost recent backup creation date is within the last 14 days.\n", + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7009,14 +5027,14 @@ }, { "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user\ninterface, the authentication process MUST require multiple\nidentifying factors for authentication.", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7071,14 +5089,14 @@ }, { "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API\nendpoint, the authentication process MUST require a credential\nsuch as an API key or token AND originate from within the trust\nperimeter.", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7142,14 +5160,14 @@ }, { "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through\na user interface, the authentication process MUST require multiple\nidentifying factors from the user.", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7202,14 +5220,14 @@ }, { "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through\nan API endpoint, the authentication process MUST require a\ncredential such as an API key or token AND originate from within\nthe trust perimeter.", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity\nfactors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7272,14 +5290,14 @@ }, { "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child\nresource, the service MUST block requests from unauthorized\nentities.", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7351,14 +5369,14 @@ }, { "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST refuse requests\nfrom unauthorized entities.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7433,14 +5451,14 @@ }, { "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource in a multi-tenant environment, the\nservice MUST refuse requests across tenant boundaries unless the\norigin is explicitly included in a pre-approved allowlist.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7512,14 +5530,14 @@ }, { "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "", "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7595,14 +5613,14 @@ }, { "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter,\nthe service MUST NOT provide any response that may indicate the\nservice exists.", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-red" ], @@ -7673,14 +5691,14 @@ }, { "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the\nservice MUST refuse requests from unauthorized entities.", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures\nthat only trusted and authenticated entities can access\nresources. These controls establish strong authentication,\nenforce multi-factor verification, and restrict access to\napproved sources to prevent unauthorized use or data exfiltration.\n", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least\nprivilege to restrict access to authorized entities from explicitly\ntrusted sources only.", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", "Applicability": [ "tlp-green", "tlp-amber", @@ -7757,14 +5775,14 @@ }, { "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on\nthe service or a child resource, the service MUST log the client\nidentity, time, and result of the attempt.", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-clear", "tlp-green", @@ -7819,14 +5837,14 @@ }, { "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-amber", "tlp-red" @@ -7879,14 +5897,14 @@ }, { "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child\nresource, the service MUST log the client identity, time, and\nresult of the attempt.", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN04 Log All Access and Changes", "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed\naudit trail for security and compliance purposes.", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", "Applicability": [ "tlp-red" ], @@ -7938,19 +5956,19 @@ }, { "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish\nan event to a monitored channel which includes the client\nidentity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns\nindicative of enumeration activities, such as repeated access\nattempts, requests, or liveness probes. Configure alerts to notify\nsecurity teams of any activities that merit further investigation.\n", + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -7996,21 +6014,21 @@ }, { "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the\nclient identity, time, and nature of the activity.", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", "Attributes": [ { "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access,\nchanges, and security-relevant events are captured, monitored,\nand alerted on in order to provide visibility, support\nincident response, and meet compliance requirements.\n", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when\nunusual enumeration activity is detected that may indicate\nreconnaissance activities.", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration\nactivities, including client identity, timestamps, and activity\nnature. Retain logs according to organizational policies, and\noccasionally review them for patterns that may indicate\nreconnaissance activities.\n", + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", "SectionThreatMappings": [ { "ReferenceId": "CCC", From 4cadee7bb1f44b2f0bfaca444e00148e118d71b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:48:21 +0200 Subject: [PATCH 16/57] chore(github): update codeowners file (#8960) --- .github/CODEOWNERS | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 74e9332e34..b953610fa1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,28 @@ +# SDK /* @prowler-cloud/sdk -/.github/ @prowler-cloud/sdk -prowler @prowler-cloud/sdk @prowler-cloud/detection-and-remediation -tests @prowler-cloud/sdk @prowler-cloud/detection-and-remediation -api @prowler-cloud/api -ui @prowler-cloud/ui \ No newline at end of file +/prowler/ @prowler-cloud/sdk @prowler-cloud/detection-and-remediation +/tests/ @prowler-cloud/sdk @prowler-cloud/detection-and-remediation +/dashboard/ @prowler-cloud/sdk +/docs/ @prowler-cloud/sdk +/examples/ @prowler-cloud/sdk +/util/ @prowler-cloud/sdk +/contrib/ @prowler-cloud/sdk +/permissions/ @prowler-cloud/sdk +/codecov.yml @prowler-cloud/sdk @prowler-cloud/api + +# API +/api/ @prowler-cloud/api + +# UI +/ui/ @prowler-cloud/ui + +# AI +/mcp_server/ @prowler-cloud/ai + +# Platform +/.github/ @prowler-cloud/platform +/Makefile @prowler-cloud/platform +/kubernetes/ @prowler-cloud/platform +**/Dockerfile* @prowler-cloud/platform +**/docker-compose*.yml @prowler-cloud/platform +**/docker-compose*.yaml @prowler-cloud/platform From a79910a694402ef208f6fa780ce04b92ed3cfda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 21 Oct 2025 12:45:31 +0200 Subject: [PATCH 17/57] chore(aws): enhance metadata for `cloudtrail` service (#8831) Co-authored-by: HugoPBrito --- prowler/CHANGELOG.md | 1 + ...l_bucket_requires_mfa_delete.metadata.json | 27 ++++++++----- ...l_cloudwatch_logging_enabled.metadata.json | 34 +++++++++------- .../cloudtrail_insights_exist.metadata.json | 33 ++++++++------- ...trail_kms_encryption_enabled.metadata.json | 33 ++++++++------- ..._log_file_validation_enabled.metadata.json | 33 +++++++++------ ...ucket_access_logging_enabled.metadata.json | 31 ++++++++------ ...t_is_not_publicly_accessible.metadata.json | 40 +++++++++++-------- ...udtrail_multi_region_enabled.metadata.json | 30 ++++++++------ ...ed_logging_management_events.metadata.json | 31 ++++++++------ ...l_s3_dataevents_read_enabled.metadata.json | 34 ++++++++++------ ..._s3_dataevents_write_enabled.metadata.json | 32 ++++++++++----- ...threat_detection_enumeration.metadata.json | 33 ++++++++++----- ...threat_detection_llm_jacking.metadata.json | 37 +++++++++++------ ...tection_privilege_escalation.metadata.json | 28 ++++++++----- 15 files changed, 280 insertions(+), 177 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 8e07798608..21336d4d3c 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -32,6 +32,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS AppStream service metadata to new format [(#8789)](https://github.com/prowler-cloud/prowler/pull/8789) - Update AWS API Gateway service metadata to new format [(#8788)](https://github.com/prowler-cloud/prowler/pull/8788) - Update AWS Athena service metadata to new format [(#8790)](https://github.com/prowler-cloud/prowler/pull/8790) +- Update AWS CloudTrail service metadata to new format [(#8831)](https://github.com/prowler-cloud/prowler/pull/8831) - Update AWS Auto Scaling service metadata to new format [(#8824)](https://github.com/prowler-cloud/prowler/pull/8824) - Update AWS Backup service metadata to new format [(#8826)](https://github.com/prowler-cloud/prowler/pull/8826) - Update AWS CloudFormation service metadata to new format [(#8828)](https://github.com/prowler-cloud/prowler/pull/8828) diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_bucket_requires_mfa_delete/cloudtrail_bucket_requires_mfa_delete.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_bucket_requires_mfa_delete/cloudtrail_bucket_requires_mfa_delete.metadata.json index 1f8ec97e29..536d0070e0 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_bucket_requires_mfa_delete/cloudtrail_bucket_requires_mfa_delete.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_bucket_requires_mfa_delete/cloudtrail_bucket_requires_mfa_delete.metadata.json @@ -1,33 +1,38 @@ { "Provider": "aws", "CheckID": "cloudtrail_bucket_requires_mfa_delete", - "CheckTitle": "Ensure the S3 bucket CloudTrail bucket requires MFA delete", + "CheckTitle": "CloudTrail trail S3 bucket has MFA delete enabled", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure the S3 bucket CloudTrail bucket requires MFA", - "Risk": "If the S3 bucket CloudTrail bucket does not require MFA, it can be deleted by an attacker.", + "Description": "**CloudTrail log buckets** for actively logging trails are evaluated for **MFA Delete** on the associated S3 bucket. The assessment determines whether `MFA Delete` is configured on the in-account log bucket; *if the bucket resides in another account, its configuration should be verified separately*.", + "Risk": "Without **MFA Delete**, stolen or over-privileged credentials can permanently delete log versions or change versioning, compromising log **integrity** and **availability**. This enables attacker cover-ups, hinders **forensics**, and weakens evidence for investigations.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html", + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudTrail/cloudtrail-bucket-mfa-delete-enabled.html" + ], "Remediation": { "Code": { - "CLI": "aws s3api put-bucket-versioning --bucket DOC-EXAMPLE-BUCKET1 --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa \"SERIAL 123456\"", + "CLI": "aws s3api put-bucket-versioning --bucket --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa \" \"", "NativeIaC": "", - "Other": "", + "Other": "1. Sign in to the AWS Management Console as the root user with MFA enabled\n2. Open AWS CloudShell (from the top navigation bar)\n3. Run:\n ```bash\n aws s3api put-bucket-versioning --bucket --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa \" \"\n ```", "Terraform": "" }, "Recommendation": { - "Text": "Configure MFA Delete for the S3 bucket CloudTrail bucket", - "Url": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html" + "Text": "Enable `MFA Delete` on the CloudTrail log bucket with versioning enabled. Enforce **least privilege** so only tightly controlled identities can delete or alter logs, and require MFA for such actions. Apply **defense in depth** using a dedicated logging account and log file integrity validation.", + "Url": "https://hub.prowler.com/check/cloudtrail_bucket_requires_mfa_delete" } }, "Categories": [ + "identity-access", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json index 9f9339038f..00c76fcfba 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_cloudwatch_logging_enabled/cloudtrail_cloudwatch_logging_enabled.metadata.json @@ -1,35 +1,39 @@ { "Provider": "aws", "CheckID": "cloudtrail_cloudwatch_logging_enabled", - "CheckTitle": "Ensure CloudTrail trails are integrated with CloudWatch Logs", + "CheckTitle": "CloudTrail trail has delivered logs to CloudWatch Logs in the last 24 hours", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail trails are integrated with CloudWatch Logs", - "Risk": "Sending CloudTrail logs to CloudWatch Logs will facilitate real-time and historic activity logging based on user, API, resource, and IP address, and provides opportunity to establish alarms and notifications for anomalous or sensitivity account activity.", + "Description": "**CloudTrail trails** are configured to send events to **CloudWatch Logs**, and show recent delivery within the last `24h`. Trails without integration or without recent CloudWatch delivery are identified, across single-Region and multi-Region trails.", + "Risk": "Missing or stale CloudWatch delivery weakens visibility and delays detection, impacting confidentiality and integrity. Adversaries can:\n- Hide **privilege escalation**\n- Perform unauthorized **resource changes**\n- Exfiltrate data via API misuse", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.prowler.com/checks/aws/logging-policies/logging_4#aws-console", + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail update-trail --name --cloudwatch-logs-log-group- arn --cloudwatch-logs-role-arn ", - "NativeIaC": "", - "Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_4#aws-console", - "Terraform": "" + "CLI": "aws cloudtrail update-trail --name --cloud-watch-logs-log-group-arn --cloud-watch-logs-role-arn ", + "NativeIaC": "```yaml\n# CloudFormation: enable CloudTrail delivery to CloudWatch Logs\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n S3BucketName: \"\"\n CloudWatchLogsLogGroupArn: \"\" # CRITICAL: sends CloudTrail events to CloudWatch Logs\n CloudWatchLogsRoleArn: \"\" # CRITICAL: role CloudTrail assumes to deliver events\n```", + "Other": "1. In AWS Console, go to CloudTrail > Trails and select the trail\n2. In the CloudWatch Logs section, click Edit\n3. Set CloudWatch Logs to Enabled\n4. Choose an existing Log group (or create new) and select an IAM role with permissions for CreateLogStream/PutLogEvents\n5. Click Save changes\n6. After a few minutes, verify events appear in the chosen CloudWatch Logs log group", + "Terraform": "```hcl\n# Terraform: enable CloudTrail delivery to CloudWatch Logs\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n cloud_watch_logs_group_arn = \"\" # CRITICAL: sends CloudTrail events to CloudWatch Logs\n cloud_watch_logs_role_arn = \"\" # CRITICAL: role CloudTrail assumes to deliver events\n}\n```" }, "Recommendation": { - "Text": "Validate that the trails in CloudTrail have an arn set in the CloudWatchLogsLogGroupArn property.", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html" + "Text": "Integrate every trail with **CloudWatch Logs** and maintain continuous, near-real-time delivery. Enforce **least privilege** on the delivery role, prefer **multi-Region** coverage, and implement **metric filters and alerts** for sensitive actions. Centralize retention to support **defense in depth**.", + "Url": "https://hub.prowler.com/check/cloudtrail_cloudwatch_logging_enabled" } }, "Categories": [ - "forensics-ready", - "logging" + "logging", + "forensics-ready" ], "DependsOn": [], "RelatedTo": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_insights_exist/cloudtrail_insights_exist.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_insights_exist/cloudtrail_insights_exist.metadata.json index 1bff51d44e..9ca93b1bc7 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_insights_exist/cloudtrail_insights_exist.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_insights_exist/cloudtrail_insights_exist.metadata.json @@ -1,34 +1,39 @@ { "Provider": "aws", "CheckID": "cloudtrail_insights_exist", - "CheckTitle": "Ensure CloudTrail Insight is enabled", + "CheckTitle": "CloudTrail trail has Insights enabled", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail Insight is enabled", - "Risk": "CloudTrail Insights provides a powerful way to search and analyze CloudTrail log data using pre-built queries and machine learning algorithms. This can help you to identify potential security threats and suspicious activity in near real-time, such as unauthorized access attempts, policy changes, or resource modifications.", + "Description": "**CloudTrail trails** that are logging are evaluated for **Insights** via `insight selectors`, which enable anomaly detection on management-event patterns (API call and error rates). The finding pinpoints logging trails where these selectors are missing.", + "Risk": "Without **Insights**, abnormal API call or error rates can go unnoticed, delaying detection of credential abuse, privilege escalation, or runaway automation. Attackers may rapidly alter policies, delete resources, or exfiltrate data before response, impacting confidentiality and availability.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-insights-events-with-cloudtrail.html", + "https://awscli.amazonaws.com/v2/documentation/api/2.18.18/reference/cloudtrail/put-insight-selectors.html", + "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail" + ], "Remediation": { "Code": { - "CLI": "", - "NativeIaC": "", - "Other": "", - "Terraform": "" + "CLI": "aws cloudtrail put-insight-selectors --trail-name --insight-selectors '[{\"InsightType\":\"ApiCallRateInsight\"}]'", + "NativeIaC": "```yaml\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n TrailName: \n S3BucketName: \n IsLogging: true\n InsightSelectors:\n - InsightType: ApiCallRateInsight # Critical fix: enables CloudTrail Insights on the trail\n```", + "Other": "1. In the AWS Console, go to CloudTrail > Trails\n2. Select the trail that is logging\n3. Click Edit on the CloudTrail Insights section\n4. Enable Insights and select API call rate (or Error rate)\n5. Save changes", + "Terraform": "```hcl\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n enable_logging = true\n\n insight_selector {\n insight_type = \"ApiCallRateInsight\" # Critical fix: enables CloudTrail Insights on the trail\n }\n}\n```" }, "Recommendation": { - "Text": "Enable CloudTrail Insight", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-insights-events-with-cloudtrail.html" + "Text": "Enable **CloudTrail Insights** on all logging trails (ideally all-Region or organization trails). Activate both `ApiCallRateInsight` and `ApiErrorRateInsight`. Integrate alerts with monitoring and review anomalies regularly. Apply **defense in depth** and least privilege to reduce potential blast radius.", + "Url": "https://hub.prowler.com/check/cloudtrail_insights_exist" } }, "Categories": [ - "forensics-ready" + "threat-detection" ], "DependsOn": [], "RelatedTo": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json index d20a08ad78..25e995b18f 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_kms_encryption_enabled/cloudtrail_kms_encryption_enabled.metadata.json @@ -1,34 +1,39 @@ { "Provider": "aws", "CheckID": "cloudtrail_kms_encryption_enabled", - "CheckTitle": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs", + "CheckTitle": "CloudTrail trail logs are encrypted at rest with a KMS key", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs", - "Risk": "By default, the log files delivered by CloudTrail to your bucket are encrypted by Amazon server-side encryption with Amazon S3-managed encryption keys (SSE-S3). To provide a security layer that is directly manageable, you can instead use server-side encryption with AWS KMS–managed keys (SSE-KMS) for your CloudTrail log files.", + "Description": "**AWS CloudTrail trails** are evaluated for use of **SSE-KMS** with a customer-managed KMS key to encrypt delivered log files at rest in S3. Trails without a configured KMS key are identified. *Applies to single-Region and multi-Region trails.*", + "Risk": "Absent a **customer-managed KMS key**, log protection relies only on storage permissions. Bucket misconfigurations or stolen credentials can expose audit data, aiding evasion and lateral movement. Missing key-level controls, rotation, and usage audit weaken **confidentiality** and **forensic integrity**.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html", + "https://trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudTrail/cloudtrail-logs-encrypted.html", + "https://www.stream.security/rules/ensure-cloudtrail-logs-are-encrypted-at-rest", + "https://www.clouddefense.ai/compliance-rules/cis-v130/logging/cis-v130-3-7" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail update-trail --name --kms-id aws kms put-key-policy --key-id --policy ", - "NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_7#fix---buildtime", - "Other": "", - "Terraform": "" + "CLI": "aws cloudtrail update-trail --name --kms-key-id ", + "NativeIaC": "```yaml\n# CloudFormation: enable KMS encryption for an existing/new CloudTrail\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n S3BucketName: \n KmsKeyId: # Critical: sets the KMS key to encrypt CloudTrail logs at rest\n```", + "Other": "1. In the AWS Console, go to CloudTrail > Trails\n2. Select the trail , click Edit\n3. Under Log file encryption, choose Use a KMS key and select \n4. Click Save changes", + "Terraform": "```hcl\n# Enable KMS encryption for CloudTrail\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n kms_key_id = \"\" # Critical: uses this KMS key to encrypt CloudTrail logs\n}\n```" }, "Recommendation": { - "Text": "This approach has the following advantages: You can create and manage the CMK encryption keys yourself. You can use a single CMK to encrypt and decrypt log files for multiple accounts across all regions. You have control over who can use your key for encrypting and decrypting CloudTrail log files. You can assign permissions for the key to the users. You have enhanced security.", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html" + "Text": "Enable **SSE-KMS** on every trail using a **customer-managed KMS key**. Apply **least privilege** so only authorized roles can `Decrypt`, and enforce **separation of duties** between key admins and log readers. Rotate keys and monitor key usage to provide **defense in depth** for CloudTrail data.", + "Url": "https://hub.prowler.com/check/cloudtrail_kms_encryption_enabled" } }, "Categories": [ - "forensics-ready", "encryption" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json index 95e0fdd478..4ea5fa8f23 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_log_file_validation_enabled/cloudtrail_log_file_validation_enabled.metadata.json @@ -1,33 +1,40 @@ { "Provider": "aws", "CheckID": "cloudtrail_log_file_validation_enabled", - "CheckTitle": "Ensure CloudTrail log file validation is enabled", + "CheckTitle": "CloudTrail trail has log file validation enabled", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail log file validation is enabled", - "Risk": "Enabling log file validation will provide additional integrity checking of CloudTrail logs. ", + "Description": "**AWS CloudTrail trails** are evaluated for **log file integrity validation** being enabled (`LogFileValidationEnabled`).\n\nWhen enabled, CloudTrail generates signed digest files to verify that S3-delivered log files remain unchanged.", + "Risk": "Without validation, adversaries can alter, forge, or delete audit entries without detection, compromising log **integrity** and non-repudiation.\n\nThis impairs investigations, enables alert evasion, and obscures unauthorized changes across regions or accounts.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-file-validation-intro.html", + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-file-validation-enabling.html", + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudTrail/cloudtrail-log-file-integrity-validation.html", + "https://deepwiki.com/acantril/learn-cantrill-io-labs/7.1-cloudtrail-log-file-integrity" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail update-trail --name --enable-log-file-validation", - "NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_2#cloudformation", - "Other": "", - "Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_2#terraform" + "CLI": "aws cloudtrail update-trail --name --enable-log-file-validation", + "NativeIaC": "```yaml\n# CloudFormation: Enable log file validation on a CloudTrail trail\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n S3BucketName: \n EnableLogFileValidation: true # Critical: enables integrity validation for delivered log files\n```", + "Other": "1. Open the AWS Console and go to CloudTrail\n2. Click Trails and select \n3. Click Edit\n4. In Additional/Advanced settings, check Enable log file validation\n5. Click Save changes", + "Terraform": "```hcl\n# Enable log file validation on a CloudTrail trail\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n enable_log_file_validation = true # Critical: ensures CloudTrail writes signed digests to detect tampering\n}\n```" }, "Recommendation": { - "Text": "Ensure LogFileValidationEnabled is set to true for each trail.", - "Url": "http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-filevalidation-enabling.html" + "Text": "Enable **log file integrity validation** on all trails (`LogFileValidationEnabled=true`).\n\nEnforce **least privilege** on the logs bucket, retain and protect digest files (e.g., S3 Object Lock/MFA Delete), and monitor validation results to support **defense in depth**.", + "Url": "https://hub.prowler.com/check/cloudtrail_log_file_validation_enabled" } }, "Categories": [ + "logging", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json index 1dba458d47..6314d80dbd 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_access_logging_enabled/cloudtrail_logs_s3_bucket_access_logging_enabled.metadata.json @@ -1,33 +1,38 @@ { "Provider": "aws", "CheckID": "cloudtrail_logs_s3_bucket_access_logging_enabled", - "CheckTitle": "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket", + "CheckTitle": "CloudTrail trail destination S3 bucket has access logging enabled", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket", - "Risk": "Server access logs can assist you in security and access audits, help you learn about your customer base, and understand your Amazon S3 bill.", + "Description": "CloudTrail trails deliver logs to an S3 bucket; this evaluates whether that bucket has **S3 server access logging** enabled to record requests against it.\n\n*If the destination bucket is outside the account or audit scope, a manual review is indicated.*", + "Risk": "Without access logging on the CloudTrail logs bucket, access and changes to log files lack an independent audit trail. Attackers could read, delete, or replace logs without attribution, undermining **log confidentiality** and **integrity**, and slowing **incident response**.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudtrail-controls.html", + "https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html" + ], "Remediation": { "Code": { - "CLI": "", - "NativeIaC": "", - "Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_6#aws-console", - "Terraform": "" + "CLI": "aws s3api put-bucket-logging --bucket --bucket-logging-status \"{\\\"LoggingEnabled\\\":{\\\"TargetBucket\\\":\\\"\\\"}}\"", + "NativeIaC": "```yaml\n# CloudFormation: enable S3 access logging on the CloudTrail destination bucket\nResources:\n :\n Type: AWS::S3::Bucket\n\n :\n Type: AWS::S3::Bucket\n Properties:\n LoggingConfiguration:\n DestinationBucketName: !Ref # Critical: turns on server access logging to this destination bucket\n # This enables access logging so the check passes\n```", + "Other": "1. In the AWS Console, go to S3 and open the bucket used by your CloudTrail trail\n2. Select the Properties tab\n3. In Server access logging, click Edit\n4. Enable logging and choose a different destination S3 bucket for the logs\n5. Click Save changes", + "Terraform": "```hcl\n# Enable access logging on the CloudTrail S3 bucket\nresource \"aws_s3_bucket\" \"\" {\n bucket = \"\"\n}\n\nresource \"aws_s3_bucket\" \"\" {\n bucket = \"\"\n}\n\nresource \"aws_s3_bucket_logging\" \"\" {\n bucket = aws_s3_bucket..id\n target_bucket = aws_s3_bucket..id # Critical: enables server access logging to the target bucket\n}\n```" }, "Recommendation": { - "Text": "Ensure that S3 buckets have Logging enabled. CloudTrail data events can be used in place of S3 bucket logging. If that is the case, this finding can be considered a false positive.", - "Url": "https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html" + "Text": "Enable **S3 server access logging** on the CloudTrail logs bucket and write logs to a separate, tightly controlled bucket. Apply **least privilege**, enable **versioning**, and consider **Object Lock** to deter tampering. Centralize monitoring to support defense-in-depth and rapid investigation.", + "Url": "https://hub.prowler.com/check/cloudtrail_logs_s3_bucket_access_logging_enabled" } }, "Categories": [ + "logging", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json index 049820b5c5..1b0d76462b 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json @@ -1,37 +1,45 @@ { "Provider": "aws", "CheckID": "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "CheckTitle": "Ensure the S3 bucket CloudTrail logs is not publicly accessible", + "CheckTitle": "CloudTrail trail S3 bucket is not publicly accessible", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", + "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Industry and Regulatory Standards/CIS AWS Foundations Benchmark", + "Effects/Data Exposure" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "critical", - "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure the S3 bucket CloudTrail logs to is not publicly accessible", - "Risk": "Allowing public access to CloudTrail log content may aid an adversary in identifying weaknesses in the affected accounts use or configuration.", + "ResourceType": "AwsS3Bucket", + "Description": "CloudTrail log destination **S3 buckets** are inspected for ACL grants that expose data to the public `AllUsers` group.\n\nBuckets hosted in other accounts are flagged for out-of-scope review.", + "Risk": "Exposed CloudTrail logs erode **confidentiality** and **integrity**.\n\nAdversaries can harvest API activity to map accounts, roles, and keys, enabling **reconnaissance** and evasion. If write is allowed, logs can be **poisoned** or deleted, thwarting investigations and compromising incident timelines.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudTrail/cloudtrail-bucket-publicly-accessible.html", + "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html", + "https://docs.aws.amazon.com/config/latest/developerguide/cloudtrail-s3-bucket-public-access-prohibited.html", + "https://docs.panther.com/alerts/alert-runbooks/built-in-policies/aws-cloudtrail-logs-s3-bucket-not-publicly-accessible" + ], "Remediation": { "Code": { - "CLI": "", - "NativeIaC": "", - "Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_3#aws-console", - "Terraform": "" + "CLI": "aws s3api put-bucket-acl --bucket --acl private", + "NativeIaC": "```yaml\n# CloudFormation: ensure the CloudTrail S3 bucket ACL is not public\nResources:\n CloudTrailLogsBucket:\n Type: AWS::S3::Bucket\n Properties:\n BucketName: \n AccessControl: Private # CRITICAL: sets bucket ACL to private, removing any AllUsers (public) grants\n```", + "Other": "1. Open the AWS S3 Console\n2. Select the bucket used by CloudTrail\n3. Go to Permissions > Access control list (ACL)\n4. Click Edit under Public access, remove any grants to \"Everyone (public access)\" (uncheck Read/Write)\n5. Save changes", + "Terraform": "```hcl\n# Ensure the CloudTrail S3 bucket ACL is private\nresource \"aws_s3_bucket_acl\" \"fix_cloudtrail_logs_bucket\" {\n bucket = \"\"\n acl = \"private\" # CRITICAL: removes any public (AllUsers) ACL grants\n}\n```" }, "Recommendation": { - "Text": "Analyze Bucket policy to validate appropriate permissions. Ensure the AllUsers principal is not granted privileges. Ensure the AuthenticatedUsers principal is not granted privileges.", - "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html" + "Text": "Apply **least privilege** to the log bucket:\n- Enable S3 `Block Public Access` (account and bucket)\n- Remove `AllUsers`/`AuthenticatedUsers` ACLs; avoid wildcard principals\n- Permit only CloudTrail and constrain with `aws:SourceArn`\n\nUse a dedicated private bucket and monitor for permission changes.", + "Url": "https://hub.prowler.com/check/cloudtrail_logs_s3_bucket_is_not_publicly_accessible" } }, "Categories": [ - "forensics-ready", "internet-exposed" ], - "DependsOn": [], + "DependsOn": [ + "s3_bucket_public_access" + ], "RelatedTo": [], "Notes": "" } diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json index 4ec89226eb..3209e88e73 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled/cloudtrail_multi_region_enabled.metadata.json @@ -1,33 +1,37 @@ { "Provider": "aws", "CheckID": "cloudtrail_multi_region_enabled", - "CheckTitle": "Ensure CloudTrail is enabled in all regions", + "CheckTitle": "Region has at least one CloudTrail trail logging", "CheckType": [ - "Software and Configuration Checks", - "Industry and Regulatory Standards", - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "high", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail is enabled in all regions", - "Risk": "AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service.", + "Description": "**AWS CloudTrail** has at least one trail with `logging` enabled in every region. A **multi-region trail** or a regional trail counts for coverage in that region.", + "Risk": "Missing coverage in any region creates **visibility gaps**.\n\nAttackers can use lesser-monitored regions to run API actions, hide **unauthorized changes**, and exfiltrate data without audit trails, weakening **detective controls**, hindering **forensics**, and delaying response (confidentiality and integrity).", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrailconcepts.html#cloudtrail-concepts-management-events" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail create-trail --name --bucket-name --is-multi-region-trail aws cloudtrail update-trail --name --is-multi-region-trail ", - "NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#cloudformation", - "Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#aws-console", - "Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#terraform" + "CLI": "", + "NativeIaC": "```yaml\n# CloudFormation: Create a multi-region CloudTrail and start logging\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n TrailName: \n S3BucketName: \n IsMultiRegionTrail: true # Critical: applies the trail to all regions\n IsLogging: true # Critical: ensures the trail is logging\n```", + "Other": "1. In the AWS Console, go to CloudTrail > Trails\n2. If no trail exists: Click Create trail, enter a name, choose an S3 bucket, set Apply trail to all regions = Yes, then Create (logging starts)\n3. If a trail exists: Select it, click Edit, set Apply trail to all regions = Yes, Save\n4. If Status shows Not logging, click Start logging", + "Terraform": "```hcl\n# Terraform: Multi-region CloudTrail with logging enabled\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n\n is_multi_region_trail = true # Critical: applies the trail to all regions\n enable_logging = true # Critical: ensures the trail is logging\n}\n```" }, "Recommendation": { - "Text": "Ensure Logging is set to ON on all regions (even if they are not being used at the moment.", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrailconcepts.html#cloudtrail-concepts-management-events" + "Text": "Use a **multi-region CloudTrail trail** or per-region trails so `logging` is active in every region, including unused ones.\n\nCentralize logs, enforce **least privilege** to log stores, and add **defense-in-depth** with encryption, integrity validation, and retention. Continuously monitor trail health to catch gaps.", + "Url": "https://hub.prowler.com/check/cloudtrail_multi_region_enabled" } }, "Categories": [ + "logging", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled_logging_management_events/cloudtrail_multi_region_enabled_logging_management_events.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled_logging_management_events/cloudtrail_multi_region_enabled_logging_management_events.metadata.json index b4b0978ffe..babf6e85b4 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled_logging_management_events/cloudtrail_multi_region_enabled_logging_management_events.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_multi_region_enabled_logging_management_events/cloudtrail_multi_region_enabled_logging_management_events.metadata.json @@ -1,31 +1,38 @@ { "Provider": "aws", "CheckID": "cloudtrail_multi_region_enabled_logging_management_events", - "CheckTitle": "Ensure CloudTrail logging management events in All Regions", + "CheckTitle": "CloudTrail trail logs management events for read and write operations", "CheckType": [ - "CIS AWS Foundations Benchmark" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure CloudTrail logging management events in All Regions", - "Risk": "AWS CloudTrail enables governance, compliance, operational auditing, and risk auditing of your AWS account. To meet FTR requirements, you must have management events enabled for all AWS accounts and in all regions and aggregate these logs into an Amazon Simple Storage Service (Amazon S3) bucket owned by a separate AWS account.", - "RelatedUrl": "https://docs.prowler.com/checks/aws/logging-policies/logging_14", + "Description": "**CloudTrail trails** record **management events** (`read` and `write`) in every AWS region and are actively logging, using a multi-region trail or per-region coverage.", + "Risk": "Without region-wide management event logging, changes to identities, networking, and audit settings can go untracked.\n\nAdversaries can operate in overlooked regions to create resources, modify permissions, or disable logging, undermining **integrity**, **confidentiality**, and incident response.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.prowler.com/checks/aws/logging-policies/logging_14#terraform", + "https://docs.prowler.com/checks/aws/logging-policies/logging_14" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail update-trail --name --is-multi-region-trail", - "NativeIaC": "", - "Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_14", - "Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_14#terraform" + "CLI": "", + "NativeIaC": "```yaml\n# CloudFormation: enable multi-region and log management events (read & write)\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n S3BucketName: \n IsMultiRegionTrail: true # CRITICAL: apply the trail to all regions\n EventSelectors:\n - IncludeManagementEvents: true # CRITICAL: log management events\n ReadWriteType: All # CRITICAL: log both read and write\n```", + "Other": "1. In the AWS Console, go to CloudTrail > Trails and select your trail\n2. Click Edit\n3. Set Apply trail to all regions to Yes\n4. Under Management events, set Read/write events to All\n5. Click Save changes\n6. If Logging is Off, click Start logging", + "Terraform": "```hcl\n# Terraform: enable multi-region and log management events (read & write)\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n\n is_multi_region_trail = true # CRITICAL: apply the trail to all regions\n\n event_selector {\n include_management_events = true # CRITICAL: log management events\n read_write_type = \"All\" # CRITICAL: log both read & write\n }\n}\n```" }, "Recommendation": { - "Text": "Enable CloudTrail logging management events in All Regions", - "Url": "https://docs.prowler.com/checks/aws/logging-policies/logging_14" + "Text": "Enable a **multi-region CloudTrail** that logs **management events** for `read` and `write` in all regions.\n\nCentralize logs in a separate, locked-down account; apply **least privilege**, encryption, retention, and integrity validation; and protect trails and storage with tamper-evident, deny-delete controls for **defense-in-depth**.", + "Url": "https://hub.prowler.com/check/cloudtrail_multi_region_enabled_logging_management_events" } }, "Categories": [ + "logging", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json index 3e3f074c5f..31288482e7 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json @@ -1,31 +1,41 @@ { "Provider": "aws", "CheckID": "cloudtrail_s3_dataevents_read_enabled", - "CheckTitle": "Check if S3 buckets have Object-level logging for read events is enabled in CloudTrail.", + "CheckTitle": "CloudTrail trail records S3 object-level read events for all S3 buckets", "CheckType": [ - "Logging and Monitoring" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.", - "Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.", + "Description": "**CloudTrail trails** log **S3 object-level read data events** for all buckets, capturing object access (for example `GetObject`) via selectors targeting `AWS::S3::Object`", + "Risk": "Without **object-level read logging**, S3 access is opaque. Attackers or insiders can exfiltrate data via `GetObject` without audit trails, eroding **confidentiality** and hindering **forensics**, anomaly detection, and incident response.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://awswala.medium.com/enable-cloudtrail-data-events-logging-for-objects-in-an-s3-bucket-33cade51ae2b", + "https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-23", + "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-cloudtrail-logging-for-s3.html", + "https://www.plerion.com/cloud-knowledge-base/ensure-object-level-logging-for-read-events-enabled-for-s3-bucket" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail put-event-selectors --trail-name --event-selectors '[{ 'ReadWriteType': 'ReadOnly', 'IncludeManagementEvents':true, 'DataResources': [{ 'Type': 'AWS::S3::Object', 'Values': ['arn:aws:s3'] }] }]'", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-23", - "Terraform": "" + "CLI": "aws cloudtrail put-event-selectors --trail-name --event-selectors '[{\"ReadWriteType\":\"ReadOnly\",\"DataResources\":[{\"Type\":\"AWS::S3::Object\",\"Values\":[\"arn:aws:s3\"]}]}]'", + "NativeIaC": "```yaml\n# CloudFormation: enable S3 object-level READ data events for all buckets on a trail\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n S3BucketName: \n EventSelectors:\n - ReadWriteType: ReadOnly # CRITICAL: log read-only data events\n DataResources:\n - Type: AWS::S3::Object # CRITICAL: target S3 object-level events\n Values:\n - arn:aws:s3 # CRITICAL: applies to all S3 buckets/objects\n```", + "Other": "1. In the AWS Console, open CloudTrail and select Trails\n2. Open your trail and go to the Data events section\n3. Add data event for S3 and choose All current and future S3 buckets\n4. Select only Read events (or All if Read-only is unavailable)\n5. Save changes", + "Terraform": "```hcl\n# Terraform: enable S3 object-level READ data events for all buckets on a trail\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n\n event_selector {\n read_write_type = \"ReadOnly\" # CRITICAL: log read-only data events\n data_resource {\n type = \"AWS::S3::Object\" # CRITICAL: target S3 object-level events\n values = [\"arn:aws:s3\"] # CRITICAL: apply to all S3 buckets/objects\n }\n }\n}\n```" }, "Recommendation": { - "Text": "Enable logs. Create an S3 lifecycle policy. Define use cases, metrics and automated responses where applicable.", - "Url": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-cloudtrail-logging-for-s3.html" + "Text": "Enable CloudTrail **data events** for S3 objects with `ReadOnly` (or `All`) across all current and future buckets. Use a multi-Region trail, centralize logs in an encrypted bucket with lifecycle retention, and integrate monitoring/alerts to support **defense in depth** and accountable access.", + "Url": "https://hub.prowler.com/check/cloudtrail_s3_dataevents_read_enabled" } }, - "Categories": [], + "Categories": [ + "logging", + "forensics-ready" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json index 42c2801cc4..09772572a7 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json @@ -1,31 +1,41 @@ { "Provider": "aws", "CheckID": "cloudtrail_s3_dataevents_write_enabled", - "CheckTitle": "Check if S3 buckets have Object-level logging for write events is enabled in CloudTrail.", + "CheckTitle": "CloudTrail trail records all S3 object-level API operations for all buckets", "CheckType": [ - "Logging and Monitoring" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "low", "ResourceType": "AwsCloudTrailTrail", - "Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.", - "Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.", + "Description": "**CloudTrail trails** include **S3 object-level data events** for **write (or all) operations** across **all current and future buckets**, via classic or advanced selectors. This records actions like `PutObject`, `DeleteObject`, and multipart uploads at the object level.", + "Risk": "Without object-level write logging, unauthorized or accidental changes and deletions can go unobserved, undermining data **integrity** and **availability**. Forensics lose visibility into who modified or removed objects, hindering detection of ransomware, rogue automation, or insider tampering.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html", + "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-cloudtrail-logging-for-s3.html", + "https://www.go2share.net/article/s3-bucket-logging", + "https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/cloudtrail-logging-s3-info.html", + "https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-22" + ], "Remediation": { "Code": { - "CLI": "aws cloudtrail put-event-selectors --trail-name --event-selectors '[{ 'ReadWriteType': 'WriteOnly', 'IncludeManagementEvents':true, 'DataResources': [{ 'Type': 'AWS::S3::Object', 'Values': ['arn:aws:s3'] }] }]'", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-22", - "Terraform": "" + "CLI": "aws cloudtrail put-event-selectors --trail-name --event-selectors '[{\"ReadWriteType\":\"WriteOnly\",\"DataResources\":[{\"Type\":\"AWS::S3::Object\",\"Values\":[\"arn:aws:s3\"]}]}]'", + "NativeIaC": "```yaml\nResources:\n :\n Type: AWS::CloudTrail::Trail\n Properties:\n TrailName: \n S3BucketName: \n EventSelectors:\n - ReadWriteType: WriteOnly\n DataResources:\n - Type: AWS::S3::Object\n Values:\n - arn:aws:s3 # Critical: enables S3 object-level write data events for all buckets, fixing the check\n```", + "Other": "1. In the AWS Console, open CloudTrail and go to Trails\n2. Select and click Edit under Data events\n3. For Data event source, choose S3\n4. Select All current and future S3 buckets\n5. Check Write events (or All events)\n6. Click Save changes", + "Terraform": "```hcl\nresource \"aws_cloudtrail\" \"\" {\n name = \"\"\n s3_bucket_name = \"\"\n\n event_selector {\n read_write_type = \"WriteOnly\"\n data_resource {\n type = \"AWS::S3::Object\"\n values = [\"arn:aws:s3\"] # Critical: logs S3 object-level write events for all buckets to pass the check\n }\n }\n}\n```" }, "Recommendation": { - "Text": "Enable logs. Create an S3 lifecycle policy. Define use cases, metrics and automated responses where applicable.", - "Url": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-cloudtrail-logging-for-s3.html" + "Text": "Enable **CloudTrail S3 data events** for object-level **write** (and *optionally* read) across all buckets on a multi-Region trail. Apply **least privilege** to log storage, set **lifecycle** retention, and integrate alerts. Use **advanced selectors** to target sensitive buckets/operations for cost control and **defense in depth**.", + "Url": "https://hub.prowler.com/check/cloudtrail_s3_dataevents_write_enabled" } }, "Categories": [ + "logging", "forensics-ready" ], "DependsOn": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.metadata.json index d0b943254a..d41cda9430 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.metadata.json @@ -1,26 +1,37 @@ { "Provider": "aws", "CheckID": "cloudtrail_threat_detection_enumeration", - "CheckTitle": "Ensure there are no potential enumeration threats in CloudTrail", - "CheckType": [], + "CheckTitle": "CloudTrail logs show no potential enumeration activity", + "CheckType": [ + "TTPs/Discovery", + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis", + "Unusual Behaviors/User" + ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "critical", "ResourceType": "AwsCloudTrailTrail", - "Description": "This check ensures that there are no potential enumeration threats in CloudTrail.", - "Risk": "Potential enumeration threats in CloudTrail can lead to unauthorized access to resources.", + "Description": "**CloudTrail activity** is analyzed for AWS identities executing a broad mix of discovery APIs like `List*`, `Describe*`, and `Get*` within a recent time window.\n\nAn identity exceeding a configurable ratio of these actions indicates potential enumeration behavior by that principal.", + "Risk": "Concentrated discovery activity signals **reconnaissance** with valid credentials. Adversaries can map assets and policies to enable **privilege escalation**, target data stores for **exfiltration** (confidentiality), and identify services to disrupt (availability), supporting stealthy lateral movement.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://medium.com/falconforce/falconfriday-detecting-enumeration-in-aws-0xff25-orangecon-25-edition-4aee83651088", + "https://www.elastic.co/guide/en/security/8.19/aws-discovery-api-calls-via-cli-from-a-single-resource.html", + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-logging-data-events", + "https://aws.plainenglish.io/aws-cloudtrail-event-cheatsheet-a-detection-engineers-guide-to-critical-api-calls-part-1-04fb1588556f", + "https://support.icompaas.com/support/solutions/articles/62000233455-ensure-there-are-no-potential-enumeration-threats-in-cloudtrail-" + ], "Remediation": { "Code": { - "CLI": "", - "NativeIaC": "", - "Other": "", - "Terraform": "" + "CLI": "aws iam update-access-key --user-name --access-key-id --status Inactive", + "NativeIaC": "```yaml\n# CloudFormation: deny common enumeration APIs for a specific IAM user\nResources:\n DenyEnumerationPolicy:\n Type: AWS::IAM::Policy\n Properties:\n PolicyName: deny-enumeration\n PolicyDocument:\n Version: \"2012-10-17\"\n Statement:\n - Effect: Deny # CRITICAL: blocks typical enumeration calls\n Action:\n - ec2:Describe* # CRITICAL: deny EC2 describe APIs\n - iam:List* # CRITICAL: deny IAM list APIs\n - s3:List* # CRITICAL: deny S3 list APIs\n - s3:Get* # CRITICAL: deny S3 get APIs (e.g., GetBucketAcl)\n Resource: \"*\"\n Users:\n - \"\" # CRITICAL: target the enumerating user\n```", + "Other": "1. In AWS Console, go to IAM > Users and open the user shown in the alert (ARN in the finding)\n2. Select the Security credentials tab\n3. For each active Access key, click Deactivate to set status to Inactive\n4. If the activity came from an EC2 instance role: go to EC2 > Instances > select the instance > Security > IAM role > Detach IAM role\n5. Re-run the check to confirm no new enumeration events occur", + "Terraform": "```hcl\n# Deny common enumeration APIs for a specific IAM user\nresource \"aws_iam_user_policy\" \"\" {\n name = \"deny-enumeration\"\n user = \"\"\n\n policy = jsonencode({\n Version = \"2012-10-17\",\n Statement = [{\n Effect = \"Deny\", # CRITICAL: blocks typical enumeration calls\n Action = [\n \"ec2:Describe*\", # CRITICAL\n \"iam:List*\", # CRITICAL\n \"s3:List*\", # CRITICAL\n \"s3:Get*\" # CRITICAL\n ],\n Resource = \"*\"\n }]\n })\n}\n```" }, "Recommendation": { - "Text": "To remediate this issue, ensure that there are no potential enumeration threats in CloudTrail.", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-logging-data-events" + "Text": "Apply **least privilege** to limit `List*`/`Describe*`/`Get*` to necessary resources and roles; use **separation of duties**.\n- Enforce MFA and short-lived sessions\n- Use **SCPs** to curb unnecessary discovery\n- Baseline expected reads and alert on spikes as **defense in depth**", + "Url": "https://hub.prowler.com/check/cloudtrail_threat_detection_enumeration" } }, "Categories": [ diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json index 9b24373bcc..d961bc0764 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json @@ -1,30 +1,43 @@ { "Provider": "aws", "CheckID": "cloudtrail_threat_detection_llm_jacking", - "CheckTitle": "Ensure there are no potential LLM Jacking threats in CloudTrail.", - "CheckType": [], + "CheckTitle": "No potential LLM jacking activity detected in CloudTrail", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis", + "TTPs/Discovery", + "TTPs/Execution", + "TTPs/Defense Evasion", + "Effects/Resource Consumption", + "Unusual Behaviors/User" + ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "critical", "ResourceType": "AwsCloudTrailTrail", - "Description": "This check ensures that there are no potential LLM Jacking threats in CloudTrail. LLM Jacking attacks involve unauthorized access to cloud-hosted large language model (LLM) services, such as AWS Bedrock, by exploiting exposed credentials or vulnerabilities. These attacks can lead to resource hijacking, unauthorized model invocations, and high operational costs for the victim organization.", - "Risk": "Potential LLM Jacking threats in CloudTrail can lead to unauthorized access to sensitive AI models, stolen credentials, resource hijacking, or running costly workloads. Attackers may use reverse proxies or malicious credentials to sell access to models, exfiltrate sensitive data, or disrupt business operations.", - "RelatedUrl": "https://sysdig.com/blog/llmjacking-stolen-cloud-credentials-used-in-new-ai-attack/", + "Description": "**CloudTrail Bedrock activity** is analyzed per identity for a high diversity of LLM-related API calls (e.g., `InvokeModel`, `InvokeModelWithResponseStream`, `GetFoundationModelAvailability`). *If an identity's share of these actions exceeds a configured threshold over a recent window*, it is surfaced as potential **LLM-jacking** behavior.", + "Risk": "Such patterns suggest **stolen credential** abuse to drive LLM usage.\n- Availability: cost exhaustion and service disruption\n- Confidentiality: leakage of prompts/outputs and model settings\n- Integrity: misuse of permissions for broader access\nAttackers may use reverse proxies to resell access and obfuscate sources.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://furkangungor.medium.com/automating-anomaly-detection-in-aws-cloudtrail-logs-4efb2ad9b958", + "https://help.sumologic.com/docs/integrations/amazon-aws/amazon-bedrock/", + "https://dzone.com/articles/ai-powered-aws-cloudtrail-analysis-strands-agent-bedrock" + ], "Remediation": { "Code": { "CLI": "", - "NativeIaC": "", - "Other": "", - "Terraform": "" + "NativeIaC": "```yaml\n# CloudFormation SCP that blocks all Amazon Bedrock actions to stop LLM jacking\nResources:\n :\n Type: AWS::Organizations::Policy\n Properties:\n Name: \n Type: SERVICE_CONTROL_POLICY\n TargetIds:\n - \"\" # CRITICAL: Attach SCP to the root/OU/account to enforce the deny\n Content:\n Version: \"2012-10-17\"\n Statement:\n - Sid: DenyBedrock\n Effect: Deny\n Action: \"bedrock:*\" # CRITICAL: Denies all Bedrock APIs (Invoke/Converse/list/entitlements/etc.)\n Resource: \"*\" # CRITICAL: Apply deny to all resources\n```", + "Other": "1. In the AWS Console, go to Organizations > Policies > Service control policies\n2. Click Create policy\n3. Set Name to \n4. In Policy, paste a deny for Bedrock:\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [{\"Sid\":\"DenyBedrock\",\"Effect\":\"Deny\",\"Action\":\"bedrock:*\",\"Resource\":\"*\"}]\n }\n5. Save the policy and click Attach\n6. Select the target (Root, OU, or the affected account ID ) and attach the policy\n7. Wait for propagation; no further Bedrock calls will occur, and the finding will clear after the detection window elapses", + "Terraform": "```hcl\n# SCP denying all Amazon Bedrock actions; attach it to the root/OU/account to halt LLM jacking\nresource \"aws_organizations_policy\" \"main\" {\n name = \"\"\n type = \"SERVICE_CONTROL_POLICY\"\n\n content = jsonencode({\n Version = \"2012-10-17\"\n Statement = [{\n Sid = \"DenyBedrock\"\n Effect = \"Deny\"\n Action = \"bedrock:*\" // CRITICAL: blocks all Bedrock APIs (prevents further suspicious activity)\n Resource = \"*\" // CRITICAL: deny across all resources\n }]\n })\n}\n\nresource \"aws_organizations_policy_attachment\" \"attach\" {\n policy_id = aws_organizations_policy.main.id\n target_id = \"\" // CRITICAL: attach to the affected account/OU/root to enforce the deny\n}\n```" }, "Recommendation": { - "Text": "To remediate this issue, enable detailed CloudTrail logging for Bedrock API calls, monitor suspicious activities, and secure sensitive credentials. Enable logging of model invocation inputs and outputs, and restrict access using IAM policies. Review CloudTrail logs regularly for suspicious `InvokeModel` actions or unauthorized access to models.", - "Url": "https://permiso.io/blog/exploiting-hosted-models" + "Text": "Apply **least privilege** to Bedrock; restrict `Invoke*` only to required roles and deny broadly via **SCPs** where unused. Enforce **MFA** and short-lived creds; rotate/remove exposed keys. Enable **model invocation logging** and budgets/quotas. Continuously monitor for Bedrock enumeration plus invoke bursts. Use **defense in depth** across identities and networks.", + "Url": "https://hub.prowler.com/check/cloudtrail_threat_detection_llm_jacking" } }, "Categories": [ - "threat-detection" + "threat-detection", + "gen-ai" ], "DependsOn": [], "RelatedTo": [], diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.metadata.json index f5237a1948..c725d4f1bd 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.metadata.json @@ -1,26 +1,34 @@ { "Provider": "aws", "CheckID": "cloudtrail_threat_detection_privilege_escalation", - "CheckTitle": "Ensure there are no potential privilege escalation threats in CloudTrail", - "CheckType": [], + "CheckTitle": "No potential privilege escalation activity detected in CloudTrail", + "CheckType": [ + "TTPs/Privilege Escalation", + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis" + ], "ServiceName": "cloudtrail", "SubServiceName": "", - "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "ResourceIdTemplate": "", "Severity": "critical", "ResourceType": "AwsCloudTrailTrail", - "Description": "This check ensures that there are no potential privilege escalation threats in CloudTrail.", - "Risk": "Potential privilege escalation threats in CloudTrail can lead to unauthorized access to resources.", + "Description": "**CloudTrail** activity is analyzed for **identities** executing high-risk actions linked to **privilege escalation** (e.g., `Attach*Policy`, `PassRole`, `AssumeRole`, `CreateAccessKey`). Identities exceeding a configurable share of such events within a *recent time window* are highlighted for investigation.", + "Risk": "Escalation patterns can grant elevated entitlements, enabling:\n- Confidentiality loss via unauthorized data/secret access\n- Integrity compromise by changing IAM policies/roles\n- Availability impact by tampering with logging or resources\nThis also facilitates lateral movement and persistence.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/", + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-logging-data-events", + "https://signmycode.com/blog/what-is-privilege-escalation-in-aws-recommendations-to-prevent-it" + ], "Remediation": { "Code": { "CLI": "", - "NativeIaC": "", - "Other": "", - "Terraform": "" + "NativeIaC": "```yaml\n# CloudFormation: Organization SCP to block common IAM privilege-escalation actions\nResources:\n :\n Type: AWS::Organizations::Policy\n Properties:\n Name: deny-iam-privesc\n Type: SERVICE_CONTROL_POLICY\n # Critical: This SCP denies risky IAM actions often used for privilege escalation\n # Explanation: Denying these actions organization-wide prevents future privesc activity detected by CloudTrail\n Content: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Deny\",\n \"Action\": [\n \"iam:AttachUserPolicy\",\n \"iam:AttachRolePolicy\",\n \"iam:PutUserPolicy\",\n \"iam:PutRolePolicy\",\n \"iam:PutGroupPolicy\",\n \"iam:AddUserToGroup\",\n \"iam:CreateAccessKey\",\n \"iam:CreateLoginProfile\",\n \"iam:UpdateLoginProfile\",\n \"iam:UpdateAssumeRolePolicy\",\n \"iam:CreatePolicyVersion\",\n \"iam:SetDefaultPolicyVersion\",\n \"iam:PassRole\"\n ],\n \"Resource\": \"*\"\n }\n ]\n }\n Attachment:\n Type: AWS::Organizations::PolicyAttachment\n Properties:\n # Critical: Attach the SCP so it is enforced\n PolicyId: !Ref \n TargetId: # OU, Root, or Account ID\n```", + "Other": "1. In AWS Console, open IAM and identify the AWS identity shown in the Prowler finding (user or role ARN)\n2. If it is an IAM user:\n - Go to Security credentials > Access keys, set active keys to Inactive\n - Go to Permissions, detach all managed policies and delete inline policies\n - Go to Groups, remove the user from privileged groups\n - Go to Console password, delete the login profile\n3. If it is an IAM role:\n - Go to Permissions, detach managed policies and delete inline policies\n - Go to Trust relationships, remove principals that should not assume the role and save\n4. Re-run the scan after the detection window elapses to confirm no further privilege-escalation activity is detected", + "Terraform": "```hcl\n# SCP to block common IAM privilege-escalation actions\nresource \"aws_organizations_policy\" \"\" {\n name = \"deny-iam-privesc\"\n type = \"SERVICE_CONTROL_POLICY\"\n\n # Critical: Deny risky IAM actions to prevent future privesc\n # Explanation: Blocks escalation techniques commonly seen in CloudTrail\n content = jsonencode({\n Version = \"2012-10-17\",\n Statement = [\n {\n Effect = \"Deny\",\n Action = [\n \"iam:AttachUserPolicy\",\n \"iam:AttachRolePolicy\",\n \"iam:PutUserPolicy\",\n \"iam:PutRolePolicy\",\n \"iam:PutGroupPolicy\",\n \"iam:AddUserToGroup\",\n \"iam:CreateAccessKey\",\n \"iam:CreateLoginProfile\",\n \"iam:UpdateLoginProfile\",\n \"iam:UpdateAssumeRolePolicy\",\n \"iam:CreatePolicyVersion\",\n \"iam:SetDefaultPolicyVersion\",\n \"iam:PassRole\"\n ],\n Resource = \"*\"\n }\n ]\n })\n}\n\nresource \"aws_organizations_policy_attachment\" \"_attach\" {\n # Critical: Attach the SCP so it takes effect\n policy_id = aws_organizations_policy..id\n target_id = \"\" # OU, Root, or Account ID\n}\n```" }, "Recommendation": { - "Text": "To remediate this issue, ensure that there are no potential privilege escalation threats in CloudTrail.", - "Url": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-logging-data-events" + "Text": "Apply **least privilege** and **defense in depth**:\n- Restrict `PassRole`, `Attach*Policy`, `UpdateAssumeRolePolicy`, `CreateAccessKey`\n- Enforce permission boundaries and SCPs\n- Require MFA and change approvals\n- Use multi-Region CloudTrail, immutable retention, and alerting on anomalous sequences", + "Url": "https://hub.prowler.com/check/cloudtrail_threat_detection_privilege_escalation" } }, "Categories": [ From 79888c9312e2a70289d9fd28dada1d6e45ddbc8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:22:21 +0200 Subject: [PATCH 18/57] chore(deps): bump playwright and @playwright/test in /ui (#8956) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/package-lock.json | 24 ++++++++++++------------ ui/package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index b1b02c43fb..49aaa1bfe6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -65,7 +65,7 @@ }, "devDependencies": { "@iconify/react": "5.2.1", - "@playwright/test": "1.53.2", + "@playwright/test": "1.56.1", "@types/node": "20.5.7", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", @@ -4276,13 +4276,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.2" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -15745,13 +15745,13 @@ } }, "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.2" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -15764,9 +15764,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "devOptional": true, "license": "Apache-2.0", "bin": { diff --git a/ui/package.json b/ui/package.json index 7dcce36d8c..c491fb4ba7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -79,7 +79,7 @@ }, "devDependencies": { "@iconify/react": "5.2.1", - "@playwright/test": "1.53.2", + "@playwright/test": "1.56.1", "@types/node": "20.5.7", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", From f0cba0321cc45a9439f910c3b3d28c737ad804ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:40:07 +0200 Subject: [PATCH 19/57] chore(codeql): improve API CodeQL action and settings (#8962) --- .github/codeql/api-codeql-config.yml | 13 +++++- .github/workflows/api-codeql.yml | 63 +++++++++++++--------------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/.github/codeql/api-codeql-config.yml b/.github/codeql/api-codeql-config.yml index 9ce26a3651..0925ea4a33 100644 --- a/.github/codeql/api-codeql-config.yml +++ b/.github/codeql/api-codeql-config.yml @@ -1,3 +1,12 @@ -name: "API - CodeQL Config" +name: 'API: CodeQL Config' paths: - - "api/" + - 'api/' + +paths-ignore: + - 'api/tests/**' + - 'api/**/__pycache__/**' + - 'api/**/migrations/**' + - 'api/**/*.md' + +queries: + - uses: security-and-quality diff --git a/.github/workflows/api-codeql.yml b/.github/workflows/api-codeql.yml index d0211c4caa..e9af830156 100644 --- a/.github/workflows/api-codeql.yml +++ b/.github/workflows/api-codeql.yml @@ -1,36 +1,34 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: API - CodeQL +name: 'API: CodeQL' on: push: branches: - - "master" - - "v5.*" + - 'master' + - 'v5.*' paths: - - "api/**" + - 'api/**' + - '.github/workflows/api-codeql.yml' + - '.github/codeql/api-codeql-config.yml' pull_request: branches: - - "master" - - "v5.*" + - 'master' + - 'v5.*' paths: - - "api/**" + - 'api/**' + - '.github/workflows/api-codeql.yml' + - '.github/codeql/api-codeql-config.yml' schedule: - cron: '00 12 * * *' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: - name: Analyze + name: CodeQL Security Analysis runs-on: ubuntu-latest + timeout-minutes: 30 permissions: actions: read contents: read @@ -39,21 +37,20 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'python' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + language: + - 'python' steps: - - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 - with: - languages: ${{ matrix.language }} - config-file: ./.github/codeql/api-codeql-config.yml + - name: Initialize CodeQL + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/api-codeql-config.yml - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + category: '/language:${{ matrix.language }}' From c4a0da820440347c6a343b72d8f48a04b606ea39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:40:25 +0200 Subject: [PATCH 20/57] chore(github): review and update issue templates (#8961) --- .github/ISSUE_TEMPLATE/bug_report.yml | 44 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 13 +++++++ .github/ISSUE_TEMPLATE/feature-request.yml | 44 ++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index fa4c805d85..fcba29ca1f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -3,6 +3,41 @@ description: Create a report to help us improve labels: ["bug", "status/needs-triage"] body: + - type: checkboxes + id: search + attributes: + label: Issue search + options: + - label: I have searched the existing issues and this bug has not been reported yet + required: true + - type: dropdown + id: component + attributes: + label: Which component is affected? + multiple: true + options: + - Prowler CLI/SDK + - Prowler API + - Prowler UI + - Prowler Dashboard + - Prowler MCP Server + - Documentation + - Other + validations: + required: true + - type: dropdown + id: provider + attributes: + label: Cloud Provider (if applicable) + multiple: true + options: + - AWS + - Azure + - GCP + - Kubernetes + - GitHub + - Microsoft 365 + - Not applicable - type: textarea id: reproduce attributes: @@ -78,6 +113,15 @@ body: prowler --version validations: required: true + - type: input + id: python-version + attributes: + label: Python version + description: Which Python version are you using? + placeholder: |- + python --version + validations: + required: true - type: input id: pip-version attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0cec..3b1e7d4e41 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,14 @@ blank_issues_enabled: false +contact_links: + - name: 📖 Documentation + url: https://docs.prowler.com + about: Check our comprehensive documentation for guides and tutorials + - name: 💬 GitHub Discussions + url: https://github.com/prowler-cloud/prowler/discussions + about: Ask questions and discuss with the community + - name: 🔒 Security Vulnerability + url: https://github.com/prowler-cloud/prowler/security/policy + about: Report security vulnerabilities privately + - name: 🌟 Prowler Community + url: https://goto.prowler.com/slack + about: Join our community for support and updates diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 528723b717..0ba3557f38 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -3,6 +3,42 @@ description: Suggest an idea for this project labels: ["feature-request", "status/needs-triage"] body: + - type: checkboxes + id: search + attributes: + label: Feature search + options: + - label: I have searched the existing issues and this feature has not been requested yet + required: true + - type: dropdown + id: component + attributes: + label: Which component would this feature affect? + multiple: true + options: + - Prowler CLI/SDK + - Prowler API + - Prowler UI + - Prowler Dashboard + - Prowler MCP Server + - Documentation + - New component/Integration + validations: + required: true + - type: dropdown + id: provider + attributes: + label: Related to specific cloud provider? + multiple: true + options: + - AWS + - Azure + - GCP + - Kubernetes + - GitHub + - Microsoft 365 + - All providers + - Not provider-specific - type: textarea id: Problem attributes: @@ -19,6 +55,14 @@ body: description: A clear and concise description of what you want to happen. validations: required: true + - type: textarea + id: use-case + attributes: + label: Use case and benefits + description: Who would benefit from this feature and how? + placeholder: This would help security teams by... + validations: + required: true - type: textarea id: Alternatives attributes: From 524209bdf2ac7879a2a1fefa9aee41329afad302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Jes=C3=BAs=20Pe=C3=B1a=20Rodr=C3=ADguez?= Date: Tue, 21 Oct 2025 15:24:09 +0200 Subject: [PATCH 21/57] feat(api): add provider_id__in filter for ScanSummary queries (#8951) --- api/CHANGELOG.md | 1 + api/src/backend/api/filters.py | 1 + api/src/backend/api/specs/v1.yaml | 30 +++++ api/src/backend/api/tests/test_views.py | 166 ++++++++++++++++++++++++ 4 files changed, 198 insertions(+) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 13749d8b9e..bdbcdcda6d 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler API** are documented in this file. - Support for `passed_findings` and `total_findings` fields in compliance requirement overview for accurate Prowler ThreatScore calculation [(#8582)](https://github.com/prowler-cloud/prowler/pull/8582) - Database read replica support [(#8869)](https://github.com/prowler-cloud/prowler/pull/8869) - Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) +- Add `provider_id__in` filter support to findings and findings severity overview endpoints [(#8951)](https://github.com/prowler-cloud/prowler/pull/8951) ### Changed - Now the MANAGE_ACCOUNT permission is required to modify or read user permissions instead of MANAGE_USERS [(#8281)](https://github.com/prowler-cloud/prowler/pull/8281) diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index ed706006e9..8552d30a64 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -765,6 +765,7 @@ class ComplianceOverviewFilter(FilterSet): class ScanSummaryFilter(FilterSet): inserted_at = DateFilter(field_name="inserted_at", lookup_expr="date") provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact") + provider_id__in = UUIDInFilter(field_name="scan__provider__id", lookup_expr="in") provider_type = ChoiceFilter( field_name="scan__provider__provider", choices=Provider.ProviderChoices.choices ) diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 3f0005726b..578da5f287 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -3611,6 +3611,16 @@ paths: schema: type: string format: uuid + - in: query + name: filter[provider_id__in] + schema: + type: array + items: + type: string + format: uuid + description: Multiple values may be separated by commas. + explode: false + style: form - in: query name: filter[provider_type] schema: @@ -3778,6 +3788,16 @@ paths: schema: type: string format: uuid + - in: query + name: filter[provider_id__in] + schema: + type: array + items: + type: string + format: uuid + description: Multiple values may be separated by commas. + explode: false + style: form - in: query name: filter[provider_type] schema: @@ -3980,6 +4000,16 @@ paths: schema: type: string format: uuid + - in: query + name: filter[provider_id__in] + schema: + type: array + items: + type: string + format: uuid + description: Multiple values may be separated by commas. + explode: false + style: form - in: query name: filter[provider_type] schema: diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 1b1783cc5a..3f78ce7705 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -46,6 +46,7 @@ from api.models import ( SAMLConfiguration, SAMLToken, Scan, + ScanSummary, StateChoices, Task, TenantAPIKey, @@ -5766,6 +5767,171 @@ class TestOverviewViewSet: assert service1_data["attributes"]["muted"] == 1 assert service2_data["attributes"]["muted"] == 0 + def test_overview_findings_provider_id_in_filter( + self, authenticated_client, tenants_fixture, providers_fixture + ): + tenant = tenants_fixture[0] + provider1, provider2, *_ = providers_fixture + + scan1 = Scan.objects.create( + name="scan-one", + provider=provider1, + trigger=Scan.TriggerChoices.MANUAL, + state=StateChoices.COMPLETED, + tenant=tenant, + ) + scan2 = Scan.objects.create( + name="scan-two", + provider=provider2, + trigger=Scan.TriggerChoices.MANUAL, + state=StateChoices.COMPLETED, + tenant=tenant, + ) + + ScanSummary.objects.create( + tenant=tenant, + scan=scan1, + check_id="check-provider-one", + service="service-a", + severity="high", + region="region-a", + _pass=5, + fail=1, + muted=2, + total=8, + new=5, + changed=2, + unchanged=1, + fail_new=1, + fail_changed=0, + pass_new=3, + pass_changed=2, + muted_new=1, + muted_changed=1, + ) + + ScanSummary.objects.create( + tenant=tenant, + scan=scan2, + check_id="check-provider-two", + service="service-b", + severity="medium", + region="region-b", + _pass=2, + fail=3, + muted=1, + total=6, + new=3, + changed=2, + unchanged=1, + fail_new=2, + fail_changed=1, + pass_new=1, + pass_changed=1, + muted_new=1, + muted_changed=0, + ) + + single_response = authenticated_client.get( + reverse("overview-findings"), + {"filter[provider_id__in]": str(provider1.id)}, + ) + assert single_response.status_code == status.HTTP_200_OK + single_attributes = single_response.json()["data"]["attributes"] + assert single_attributes["pass"] == 5 + assert single_attributes["fail"] == 1 + assert single_attributes["muted"] == 2 + assert single_attributes["total"] == 8 + + combined_response = authenticated_client.get( + reverse("overview-findings"), + {"filter[provider_id__in]": f"{provider1.id},{provider2.id}"}, + ) + assert combined_response.status_code == status.HTTP_200_OK + combined_attributes = combined_response.json()["data"]["attributes"] + assert combined_attributes["pass"] == 7 + assert combined_attributes["fail"] == 4 + assert combined_attributes["muted"] == 3 + assert combined_attributes["total"] == 14 + + def test_overview_findings_severity_provider_id_in_filter( + self, authenticated_client, tenants_fixture, providers_fixture + ): + tenant = tenants_fixture[0] + provider1, provider2, *_ = providers_fixture + + scan1 = Scan.objects.create( + name="severity-scan-one", + provider=provider1, + trigger=Scan.TriggerChoices.MANUAL, + state=StateChoices.COMPLETED, + tenant=tenant, + ) + scan2 = Scan.objects.create( + name="severity-scan-two", + provider=provider2, + trigger=Scan.TriggerChoices.MANUAL, + state=StateChoices.COMPLETED, + tenant=tenant, + ) + + ScanSummary.objects.create( + tenant=tenant, + scan=scan1, + check_id="severity-check-one", + service="service-a", + severity="high", + region="region-a", + _pass=4, + fail=4, + muted=0, + total=8, + ) + ScanSummary.objects.create( + tenant=tenant, + scan=scan1, + check_id="severity-check-two", + service="service-a", + severity="medium", + region="region-b", + _pass=2, + fail=2, + muted=0, + total=4, + ) + ScanSummary.objects.create( + tenant=tenant, + scan=scan2, + check_id="severity-check-three", + service="service-b", + severity="critical", + region="region-c", + _pass=1, + fail=2, + muted=0, + total=3, + ) + + single_response = authenticated_client.get( + reverse("overview-findings_severity"), + {"filter[provider_id__in]": str(provider1.id)}, + ) + assert single_response.status_code == status.HTTP_200_OK + single_attributes = single_response.json()["data"]["attributes"] + assert single_attributes["high"] == 8 + assert single_attributes["medium"] == 4 + assert single_attributes["critical"] == 0 + + combined_response = authenticated_client.get( + reverse("overview-findings_severity"), + {"filter[provider_id__in]": f"{provider1.id},{provider2.id}"}, + ) + assert combined_response.status_code == status.HTTP_200_OK + combined_attributes = combined_response.json()["data"]["attributes"] + assert combined_attributes["high"] == 8 + assert combined_attributes["medium"] == 4 + assert combined_attributes["critical"] == 3 + @pytest.mark.django_db class TestScheduleViewSet: From 000cb931578acd94aeee9d7f0375059d87583665 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Tue, 21 Oct 2025 15:49:42 +0200 Subject: [PATCH 22/57] chore: remove security template as it's already there (#8964) --- .github/ISSUE_TEMPLATE/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3b1e7d4e41..6e7c21013b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,9 +6,6 @@ contact_links: - name: 💬 GitHub Discussions url: https://github.com/prowler-cloud/prowler/discussions about: Ask questions and discuss with the community - - name: 🔒 Security Vulnerability - url: https://github.com/prowler-cloud/prowler/security/policy - about: Report security vulnerabilities privately - name: 🌟 Prowler Community url: https://goto.prowler.com/slack about: Join our community for support and updates From 34554d6123b75a0c61f9f51b3b27cf41d87d53f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 21 Oct 2025 16:03:24 +0200 Subject: [PATCH 23/57] feat(mcp): add support for production deployment with uvicorn (#8958) --- .../installation/prowler-mcp.mdx | 37 ++++++++++++-- mcp_server/.env.template | 2 +- mcp_server/CHANGELOG.md | 3 +- mcp_server/Dockerfile | 13 +++-- mcp_server/README.md | 20 ++++++-- mcp_server/entrypoint.sh | 50 +++++++++++++++++++ mcp_server/prowler_mcp_server/main.py | 25 +++++++--- .../prowler_app/utils/auth.py | 11 +++- mcp_server/prowler_mcp_server/server.py | 36 ++++++++----- 9 files changed, 159 insertions(+), 38 deletions(-) create mode 100755 mcp_server/entrypoint.sh diff --git a/docs/getting-started/installation/prowler-mcp.mdx b/docs/getting-started/installation/prowler-mcp.mdx index dfdf7d0822..8c3fd2f955 100644 --- a/docs/getting-started/installation/prowler-mcp.mdx +++ b/docs/getting-started/installation/prowler-mcp.mdx @@ -182,19 +182,19 @@ Configure the server using environment variables: |----------|-------------|----------|---------| | `PROWLER_APP_API_KEY` | Prowler API key | Only for STDIO mode | - | | `PROWLER_API_BASE_URL` | Custom Prowler API endpoint | No | `https://api.prowler.com` | -| `PROWLER_MCP_MODE` | Default transport mode (overwritten by `--transport` argument) | No | `stdio` | +| `PROWLER_MCP_TRANSPORT_MODE` | Default transport mode (overwritten by `--transport` argument) | No | `stdio` | ```bash macOS/Linux export PROWLER_APP_API_KEY="pk_your_api_key_here" export PROWLER_API_BASE_URL="https://api.prowler.com" -export PROWLER_MCP_MODE="http" +export PROWLER_MCP_TRANSPORT_MODE="http" ``` ```bash Windows PowerShell $env:PROWLER_APP_API_KEY="pk_your_api_key_here" $env:PROWLER_API_BASE_URL="https://api.prowler.com" -$env:PROWLER_MCP_MODE="http" +$env:PROWLER_MCP_TRANSPORT_MODE="http" ``` @@ -209,7 +209,7 @@ For convenience, create a `.env` file in the `mcp_server` directory: ```bash .env PROWLER_APP_API_KEY=pk_your_api_key_here PROWLER_API_BASE_URL=https://api.prowler.com -PROWLER_MCP_MODE=stdio +PROWLER_MCP_TRANSPORT_MODE=stdio ``` When using Docker, pass the environment file: @@ -228,6 +228,35 @@ uvx /path/to/prowler/mcp_server/ This is particularly useful when configuring MCP clients that need to launch the server from a specific path. +## Production Deployment + +For production deployments that require customization, it is recommended to use the ASGI application that can be found in `prowler_mcp_server.server`. This can be run with uvicorn: + +```bash +uvicorn prowler_mcp_server.server:app --host 0.0.0.0 --port 8000 +``` + +For more details on production deployment options, see the [FastMCP production deployment guide](https://gofastmcp.com/deployment/http#production-deployment) and [uvicorn settings](https://www.uvicorn.org/settings/). + +### Entrypoint Script + +The source tree includes `entrypoint.sh` to simplify switching between the +standard CLI runner and the ASGI app. The first argument selects the mode and +any additional flags are passed straight through: + +```bash +# Default CLI experience (prowler-mcp console script) +./entrypoint.sh main --transport http --host 0.0.0.0 + +# ASGI app via uvicorn +./entrypoint.sh uvicorn --host 0.0.0.0 --port 9000 +``` + +Omitting the mode defaults to `main`, matching the `prowler-mcp` console script. +When `uvicorn` mode is selected, the script exports `PROWLER_MCP_TRANSPORT_MODE=http` automatically. + +This is the default entrypoint for the Docker container. + ## Next Steps Now that you have the Prowler MCP Server installed, proceed to configure your MCP client: diff --git a/mcp_server/.env.template b/mcp_server/.env.template index 6227bdd388..7713b5ae33 100644 --- a/mcp_server/.env.template +++ b/mcp_server/.env.template @@ -1,3 +1,3 @@ PROWLER_APP_API_KEY="pk_your_api_key_here" PROWLER_API_BASE_URL="https://api.prowler.com" -PROWLER_MCP_MODE="stdio" +PROWLER_MCP_TRANSPORT_MODE="stdio" diff --git a/mcp_server/CHANGELOG.md b/mcp_server/CHANGELOG.md index be766b4162..490c1878e9 100644 --- a/mcp_server/CHANGELOG.md +++ b/mcp_server/CHANGELOG.md @@ -13,4 +13,5 @@ All notable changes to the **Prowler MCP Server** are documented in this file. - Add new MCP Server for Prowler Documentation [(#8795)](https://github.com/prowler-cloud/prowler/pull/8795) - API key support for STDIO mode and enhanced HTTP mode authentication [(#8823)](https://github.com/prowler-cloud/prowler/pull/8823) - Add health check endpoint [(#8905)](https://github.com/prowler-cloud/prowler/pull/8905) -- Update Prowler Documentation MCP Server to use Mintlify API [(#8915)](https://github.com/prowler-cloud/prowler/pull/8915) +- Update Prowler Documentation MCP Server to use Mintlify API [(#8916)](https://github.com/prowler-cloud/prowler/pull/8916) +- Add custom production deployment using uvicorn [(#8958)](https://github.com/prowler-cloud/prowler/pull/8958) diff --git a/mcp_server/Dockerfile b/mcp_server/Dockerfile index fd92db7cdd..d075e83b5f 100644 --- a/mcp_server/Dockerfile +++ b/mcp_server/Dockerfile @@ -47,13 +47,12 @@ COPY --from=builder --chown=prowler /app/prowler_mcp_server /app/prowler_mcp_ser # 3. Project metadata file (may be needed by some packages at runtime) COPY --from=builder --chown=prowler /app/pyproject.toml /app/pyproject.toml +# 4. Entrypoint helper script for selecting runtime mode +COPY --from=builder --chown=prowler /app/entrypoint.sh /app/entrypoint.sh + # Add virtual environment to PATH so prowler-mcp command is available ENV PATH="/app/.venv/bin:$PATH" -# Entry point for the MCP server -# Default to stdio mode, but allow overriding via command arguments -# Examples: -# docker run -p 8000:8000 prowler-mcp --transport http --host 0.0.0.0 --port 8000 -# docker run prowler-mcp --transport stdio -ENTRYPOINT ["prowler-mcp"] -CMD ["--transport", "stdio"] +# Entrypoint wrapper defaults to CLI mode; override with `uvicorn` to run ASGI app +ENTRYPOINT ["/app/entrypoint.sh"] +CMD ["main"] diff --git a/mcp_server/README.md b/mcp_server/README.md index eebe139fba..e652ee5fcc 100644 --- a/mcp_server/README.md +++ b/mcp_server/README.md @@ -144,11 +144,11 @@ uv run prowler-mcp --transport http uv run prowler-mcp --transport http --host 0.0.0.0 --port 8080 ``` -For self-deployed MCP remote server, you can use also configure the server to use a custom API base URL with the environment variable `PROWLER_API_BASE_URL`; and the transport mode with the environment variable `PROWLER_MCP_MODE`. +For self-deployed MCP remote server, you can use also configure the server to use a custom API base URL with the environment variable `PROWLER_API_BASE_URL`; and the transport mode with the environment variable `PROWLER_MCP_TRANSPORT_MODE`. ```bash export PROWLER_API_BASE_URL="https://api.prowler.com" -export PROWLER_MCP_MODE="http" +export PROWLER_MCP_TRANSPORT_MODE="http" ``` ### Using uv directly @@ -190,6 +190,16 @@ docker run --rm --env-file ./.env -p 8000:8000 -it prowler-mcp --transport http docker run --rm --env-file ./.env -p 8080:8080 -it prowler-mcp --transport http --host 0.0.0.0 --port 8080 ``` +## Production Deployment + +For production deployments that require customization, it is recommended to use the ASGI application that can be found in `prowler_mcp_server.server`. This can be run with uvicorn: + +```bash +uvicorn prowler_mcp_server.server:app --host 0.0.0.0 --port 8000 +``` + +For more details on production deployment options, see the [FastMCP production deployment guide](https://gofastmcp.com/deployment/http#production-deployment) and [uvicorn settings](https://www.uvicorn.org/settings/). + ## Command Line Arguments The Prowler MCP server supports the following command line arguments: @@ -482,6 +492,10 @@ If you want to have it globally available, add the example server to Cursor's co If you want to have it only for the current project, add the example server to the project's root in a new `.cursor/mcp.json` file. +## Documentation + +For detailed documentation about the Prowler MCP Server, including guides, tutorials, and use cases, visit the [official Prowler documentation](https://docs.prowler.com). + ## License -This project follows the repository’s main license. See the [LICENSE](../LICENSE) file at the repository root. +This project follows the repository's main license. See the [LICENSE](../LICENSE) file at the repository root. diff --git a/mcp_server/entrypoint.sh b/mcp_server/entrypoint.sh new file mode 100755 index 0000000000..7b56e08708 --- /dev/null +++ b/mcp_server/entrypoint.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu + +usage() { + cat <<'EOF' +Usage: ./entrypoint.sh [main|uvicorn] [args...] + +Modes: + main (default) Run prowler-mcp + uvicorn Run uvicorn prowler_mcp_server.server:app + +All additional arguments are forwarded to the selected command. +EOF +} + +mode="main" + +if [ "$#" -gt 0 ]; then + case "$1" in + main|cli) + mode="main" + shift + ;; + uvicorn|asgi) + mode="uvicorn" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + mode="main" + ;; + esac +fi + +case "$mode" in + main) + exec prowler-mcp "$@" + ;; + uvicorn) + export PROWLER_MCP_TRANSPORT_MODE="http" + exec uvicorn prowler_mcp_server.server:app "$@" + ;; + *) + usage + exit 1 + ;; +esac diff --git a/mcp_server/prowler_mcp_server/main.py b/mcp_server/prowler_mcp_server/main.py index 272cfa3043..596e1c31b9 100644 --- a/mcp_server/prowler_mcp_server/main.py +++ b/mcp_server/prowler_mcp_server/main.py @@ -1,10 +1,8 @@ import argparse -import asyncio import os import sys from prowler_mcp_server.lib.logger import logger -from prowler_mcp_server.server import setup_main_server def parse_arguments(): @@ -13,7 +11,7 @@ def parse_arguments(): parser.add_argument( "--transport", choices=["stdio", "http"], - default=os.getenv("PROWLER_MCP_MODE", "stdio"), + default=None, help="Transport method (default: stdio)", ) parser.add_argument( @@ -35,13 +33,26 @@ def main(): try: args = parse_arguments() - # Set up server with configuration - prowler_mcp_server = asyncio.run(setup_main_server(transport=args.transport)) + print(f"args.transport: {args.transport}") + + if args.transport is None: + args.transport = os.getenv("PROWLER_MCP_TRANSPORT_MODE", "stdio") + else: + os.environ["PROWLER_MCP_TRANSPORT_MODE"] = args.transport + + from prowler_mcp_server.server import prowler_mcp_server if args.transport == "stdio": - prowler_mcp_server.run(transport="stdio") + prowler_mcp_server.run(transport=args.transport, show_banner=False) elif args.transport == "http": - prowler_mcp_server.run(transport="http", host=args.host, port=args.port) + prowler_mcp_server.run( + transport=args.transport, + host=args.host, + port=args.port, + show_banner=False, + ) + else: + logger.error(f"Invalid transport: {args.transport}") except KeyboardInterrupt: logger.info("Shutting down Prowler MCP server...") diff --git a/mcp_server/prowler_mcp_server/prowler_app/utils/auth.py b/mcp_server/prowler_mcp_server/prowler_app/utils/auth.py index 1fdc23d431..b23c58d016 100644 --- a/mcp_server/prowler_mcp_server/prowler_app/utils/auth.py +++ b/mcp_server/prowler_mcp_server/prowler_app/utils/auth.py @@ -14,7 +14,7 @@ class ProwlerAppAuth: def __init__( self, - mode: str = os.getenv("PROWLER_MCP_MODE", "stdio"), + mode: str = os.getenv("PROWLER_MCP_TRANSPORT_MODE", "stdio"), base_url: str = os.getenv("PROWLER_API_BASE_URL", "https://api.prowler.com"), ): self.base_url = base_url.rstrip("/") @@ -33,7 +33,14 @@ class ProwlerAppAuth: raise ValueError("Prowler App API key format is incorrect") def _parse_jwt(self, token: str) -> Optional[Dict]: - """Parse JWT token and return payload, similar to JS parseJwt function.""" + """Parse JWT token and return payload + + Args: + token: JWT token to parse + + Returns: + Parsed JWT payload, or None if parsing fails + """ if not token: return None diff --git a/mcp_server/prowler_mcp_server/server.py b/mcp_server/prowler_mcp_server/server.py index 644135497a..044d2b5a55 100644 --- a/mcp_server/prowler_mcp_server/server.py +++ b/mcp_server/prowler_mcp_server/server.py @@ -1,16 +1,15 @@ +import asyncio import os from fastmcp import FastMCP from prowler_mcp_server.lib.logger import logger from starlette.responses import JSONResponse +prowler_mcp_server = FastMCP("prowler-mcp-server") -async def setup_main_server(transport: str) -> FastMCP: + +async def setup_main_server(): """Set up the main Prowler MCP server with all available integrations.""" - - # Initialize main Prowler MCP server - prowler_mcp_server = FastMCP("prowler-mcp-server") - # Import Prowler Hub tools with prowler_hub_ prefix try: logger.info("Importing Prowler Hub server...") @@ -21,12 +20,10 @@ async def setup_main_server(transport: str) -> FastMCP: except Exception as e: logger.error(f"Failed to import Prowler Hub server: {e}") + # Import Prowler App tools with prowler_app_ prefix try: logger.info("Importing Prowler App server...") - if os.getenv("PROWLER_MCP_MODE", None) is None: - os.environ["PROWLER_MCP_MODE"] = transport - if not os.path.exists( os.path.join(os.path.dirname(__file__), "prowler_app", "server.py") ): @@ -44,6 +41,7 @@ async def setup_main_server(transport: str) -> FastMCP: except Exception as e: logger.error(f"Failed to import Prowler App server: {e}") + # Import Prowler Documentation tools with prowler_docs_ prefix try: logger.info("Importing Prowler Documentation server...") from prowler_mcp_server.prowler_documentation.server import docs_mcp_server @@ -53,9 +51,21 @@ async def setup_main_server(transport: str) -> FastMCP: except Exception as e: logger.error(f"Failed to import Prowler Documentation server: {e}") - # Add health check endpoint - @prowler_mcp_server.custom_route("/health", methods=["GET"]) - async def health_check(request): - return JSONResponse({"status": "healthy", "service": "prowler-mcp-server"}) - return prowler_mcp_server +# Add health check endpoint +@prowler_mcp_server.custom_route("/health", methods=["GET"]) +async def health_check() -> JSONResponse: + """Health check endpoint.""" + return JSONResponse({"status": "healthy", "service": "prowler-mcp-server"}) + + +# Get or create the event loop +try: + loop = asyncio.get_running_loop() + # If we have a running loop, schedule the setup as a task + loop.create_task(setup_main_server()) +except RuntimeError: + # No running loop, use asyncio.run (for standalone execution) + asyncio.run(setup_main_server()) + +app = prowler_mcp_server.http_app() From 5c9e9bc86a699babe55e2ce75870103e7172b4fa Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:13:55 +0200 Subject: [PATCH 24/57] docs: fix security heading (#8965) --- docs/security.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security.mdx b/docs/security.mdx index 49fb3684bb..d8a3f63c73 100644 --- a/docs/security.mdx +++ b/docs/security.mdx @@ -16,7 +16,7 @@ We use encryption everywhere possible. The data and communications used by **Pro Prowler Cloud is GDPR compliant in regards to personal data and the ["right to be forgotten"](https://gdpr.eu/right-to-be-forgotten/). When a user deletes their account their user information will be deleted from Prowler Cloud online and backup systems within 10 calendar days. -## Software Security +## Software Security We follow a **security-by-design approach** throughout our software development lifecycle. All changes go through automated checks at every stage, from local development to production deployment. From 206f23b5a578ff9c9636ebbc638b1eedb24ae147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 21 Oct 2025 16:31:18 +0200 Subject: [PATCH 25/57] chore(aws): enhance metadata for `dms` service (#8861) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 1 + ...ngodb_authentication_enabled.metadata.json | 33 +++++++------ ...ne_iam_authorization_enabled.metadata.json | 31 ++++++++----- ...n_transit_encryption_enabled.metadata.json | 36 +++++++++------ .../dms_endpoint_ssl_enabled.metadata.json | 46 +++++++++++-------- ...inor_version_upgrade_enabled.metadata.json | 34 +++++++++----- ...ms_instance_multi_az_enabled.metadata.json | 33 +++++++------ ...ms_instance_no_public_access.metadata.json | 33 ++++++++----- ..._task_source_logging_enabled.metadata.json | 34 ++++++++------ ..._task_target_logging_enabled.metadata.json | 35 ++++++++------ 10 files changed, 197 insertions(+), 119 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 21336d4d3c..44d5ffef64 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -37,6 +37,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS Backup service metadata to new format [(#8826)](https://github.com/prowler-cloud/prowler/pull/8826) - Update AWS CloudFormation service metadata to new format [(#8828)](https://github.com/prowler-cloud/prowler/pull/8828) - Update AWS Lambda service metadata to new format [(#8825)](https://github.com/prowler-cloud/prowler/pull/8825) +- Update AWS DMS service metadata to new format [(#8861)](https://github.com/prowler-cloud/prowler/pull/8861) - Update AWS Directory Service service metadata to new format [(#8859)](https://github.com/prowler-cloud/prowler/pull/8859) - Update AWS CloudFront service metadata to new format [(#8829)](https://github.com/prowler-cloud/prowler/pull/8829) - Deprecate user authentication for M365 provider [(#8865)](https://github.com/prowler-cloud/prowler/pull/8865) diff --git a/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json index 8a0b800295..d8a57d00b5 100644 --- a/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json @@ -1,31 +1,38 @@ { "Provider": "aws", "CheckID": "dms_endpoint_mongodb_authentication_enabled", - "CheckTitle": "Check if DMS endpoints for MongoDB have an authentication mechanism enabled.", + "CheckTitle": "DMS MongoDB endpoint has an authentication mechanism enabled", "CheckType": [ - "Software and Configuration Checks/AWS Security Best Practices" + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure" ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:dms:region:account-id:endpoint/endpoint-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsEndpoint", - "Description": "This control checks whether an AWS DMS endpoint for MongoDB is configured with an authentication mechanism. The control fails if an authentication type isn't set for the endpoint.", - "Risk": "Without an authentication mechanism enabled, unauthorized users may gain access to sensitive data during migration, increasing the risk of data breaches and security incidents.", - "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MongoDB.html", + "Description": "**AWS DMS MongoDB endpoints** use an authentication mechanism. Configuration expects `AuthType` not `no` (e.g., `password`) with an `authMechanism` such as `scram_sha_1` or `mongodb_cr`.", + "Risk": "Without authentication, unauthenticated connections can access the source, degrading **confidentiality** and **integrity**. Adversaries could read or modify migrated documents, hijack CDC, inject data, or exfiltrate records during replication.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MongoDB.html", + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-11" + ], "Remediation": { "Code": { - "CLI": "aws dms modify-endpoint --endpoint-arn --username --password --authentication-type ", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-11", - "Terraform": "" + "CLI": "aws dms modify-endpoint --endpoint-arn --mongodb-settings '{\"AuthType\":\"password\"}' --username --password ", + "NativeIaC": "```yaml\n# CloudFormation: enable authentication on a MongoDB DMS endpoint\nResources:\n :\n Type: AWS::DMS::Endpoint\n Properties:\n EndpointIdentifier: \n EndpointType: source\n EngineName: mongodb\n MongoDbSettings:\n AuthType: password # CRITICAL: sets authentication mode to 'password' so auth is enabled\n```", + "Other": "1. In the AWS Console, go to Database Migration Service > Endpoints\n2. Select the MongoDB endpoint and click Modify\n3. Under MongoDB settings, set Authentication mode to Password\n4. Enter Username and Password\n5. Click Save changes", + "Terraform": "```hcl\n# Terraform: enable authentication on a MongoDB DMS endpoint\nresource \"aws_dms_endpoint\" \"\" {\n endpoint_id = \"\"\n endpoint_type = \"source\"\n engine_name = \"mongodb\"\n\n mongodb_settings {\n auth_type = \"password\" # CRITICAL: enables authentication for the MongoDB endpoint\n }\n}\n```" }, "Recommendation": { - "Text": "Enable an authentication mechanism on DMS endpoints for MongoDB to ensure secure access control during migration.", - "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MongoDB.html" + "Text": "Enforce **strong authentication** on MongoDB endpoints: set `AuthType` to `password` and use `authMechanism` like `scram_sha_1`. Apply **least privilege** database accounts, store secrets in **Secrets Manager**, and pair with **TLS** for defense in depth.", + "Url": "https://hub.prowler.com/check/dms_endpoint_mongodb_authentication_enabled" } }, - "Categories": [], + "Categories": [ + "identity-access" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/dms/dms_endpoint_neptune_iam_authorization_enabled/dms_endpoint_neptune_iam_authorization_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_neptune_iam_authorization_enabled/dms_endpoint_neptune_iam_authorization_enabled.metadata.json index 45471a9268..ef8bfd6bde 100644 --- a/prowler/providers/aws/services/dms/dms_endpoint_neptune_iam_authorization_enabled/dms_endpoint_neptune_iam_authorization_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_endpoint_neptune_iam_authorization_enabled/dms_endpoint_neptune_iam_authorization_enabled.metadata.json @@ -1,31 +1,38 @@ { "Provider": "aws", "CheckID": "dms_endpoint_neptune_iam_authorization_enabled", - "CheckTitle": "Check if DMS endpoints for Neptune databases have IAM authorization enabled.", + "CheckTitle": "DMS endpoint for Neptune has IAM authorization enabled", "CheckType": [ - "Software and Configuration Checks/AWS Security Best Practices" + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure" ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:dms:region:account-id:endpoint/endpoint-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsEndpoint", - "Description": "This control checks whether an AWS DMS endpoint for an Amazon Neptune database is configured with IAM authorization. The control fails if the DMS endpoint doesn't have IAM authorization enabled.", - "Risk": "Without IAM authorization, DMS endpoints for Neptune databases may lack granular access control, increasing the risk of unauthorized access to sensitive data.", - "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Neptune.html", + "Description": "**DMS Neptune endpoints** have **IAM authorization** enabled via the endpoint setting `IamAuthEnabled`.", + "Risk": "Without **IAM authorization**, migration components can interact with Neptune using broad trust, enabling unauthorized data loads, reads, or alterations.\n\nThis degrades **confidentiality** and **integrity** and increases the chance of privilege abuse and data exfiltration.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Neptune.html", + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-10" + ], "Remediation": { "Code": { - "CLI": "aws dms modify-endpoint --endpoint-arn --service-access-role-arn ", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-10", + "CLI": "aws dms modify-endpoint --endpoint-arn --neptune-settings '{\"IamAuthEnabled\":true}'", + "NativeIaC": "```yaml\n# CloudFormation: Enable IAM authorization on a DMS Neptune endpoint\nResources:\n :\n Type: AWS::DMS::Endpoint\n Properties:\n EndpointType: target\n EngineName: neptune\n NeptuneSettings:\n ServiceAccessRoleArn: \n S3BucketName: \n S3BucketFolder: \n IamAuthEnabled: true # Critical: enables IAM authorization for the Neptune endpoint\n```", + "Other": "1. In the AWS Console, go to Database Migration Service > Endpoints\n2. Select the Neptune endpoint and click Modify\n3. Expand Endpoint settings (Neptune settings) and set IAM authorization to Enabled\n4. Ensure Service access role ARN is set, then click Save", "Terraform": "" }, "Recommendation": { - "Text": "Enable IAM authorization on DMS endpoints for Neptune databases by specifying a service role in the ServiceAccessRoleARN parameter.", - "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Neptune.html" + "Text": "Enable **IAM authorization** on Neptune endpoints (`IamAuthEnabled=true`) and use a **least privilege** service role limited to minimal Neptune and S3 permissions.\n\nApply **defense in depth**: restrict network paths, separate duties for migration roles, and monitor access with logs and alerts.", + "Url": "https://hub.prowler.com/check/dms_endpoint_neptune_iam_authorization_enabled" } }, - "Categories": [], + "Categories": [ + "identity-access" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json index 471b27aee4..2aa1ee8a00 100644 --- a/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json @@ -1,31 +1,41 @@ { "Provider": "aws", "CheckID": "dms_endpoint_redis_in_transit_encryption_enabled", - "CheckTitle": "Check if DMS endpoints for Redis OSS are encrypted in transit.", + "CheckTitle": "DMS endpoint for Redis OSS is encrypted in transit", "CheckType": [ - "Software and Configuration Checks/AWS Security Best Practices" + "Software and Configuration Checks/AWS Security Best Practices/Encryption in Transit", + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls (USA)", + "Software and Configuration Checks/Industry and Regulatory Standards/PCI-DSS", + "Software and Configuration Checks/Industry and Regulatory Standards/ISO 27001 Controls" ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:dms:region:account-id:endpoint/endpoint-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsEndpoint", - "Description": "This control checks whether an AWS DMS endpoint for Redis OSS is configured with a TLS connection. The control fails if the endpoint doesn't have TLS enabled.", - "Risk": "Without TLS, data transmitted between databases may be vulnerable to interception or eavesdropping, increasing the risk of data breaches and other security incidents.", - "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.Redis.html", + "Description": "**DMS Redis OSS endpoints** are assessed for the presence of **TLS** in their endpoint settings, such as `ssl-encryption`, indicating encrypted connections between the DMS replication instance and Redis.", + "Risk": "Without **TLS**, traffic between DMS and Redis can be intercepted or altered, compromising **confidentiality** and **integrity**.\n\nAttackers can perform **man-in-the-middle** interception, steal auth tokens, and inject or corrupt migrated data.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-12", + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Redis.html#CHAP_Target.Redis.EndpointSettings", + "https://support.icompaas.com/support/solutions/articles/62000233450-ensure-encryption-in-transit-for-dms-endpoints-for-redis-oss" + ], "Remediation": { "Code": { - "CLI": "aws dms modify-endpoint --endpoint-arn --redis-settings '{'SslSecurityProtocol': 'ssl-encryption'}'", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-12", - "Terraform": "" + "CLI": "", + "NativeIaC": "```yaml\n# CloudFormation: Enable TLS for Redis OSS DMS endpoint\nResources:\n :\n Type: AWS::DMS::Endpoint\n Properties:\n EndpointIdentifier: \n EndpointType: target\n EngineName: redis\n RedisSettings:\n ServerName: \n Port: 6379\n AuthType: none\n SslSecurityProtocol: ssl-encryption # Critical: enables TLS for in-transit encryption\n```", + "Other": "1. In the AWS Console, go to Database Migration Service > Endpoints\n2. Select the Redis OSS endpoint and click Modify\n3. Set SSL security protocol (Encryption in transit) to \"SSL encryption\"\n4. Save changes", + "Terraform": "```hcl\n# Enable TLS for Redis OSS DMS endpoint\nresource \"aws_dms_endpoint\" \"\" {\n endpoint_id = \"\"\n endpoint_type = \"target\"\n engine_name = \"redis\"\n\n redis_settings {\n server_name = \"\"\n port = 6379\n auth_type = \"none\"\n ssl_security_protocol = \"ssl-encryption\" # Critical: enables TLS for in-transit encryption\n }\n}\n```" }, "Recommendation": { - "Text": "Enable TLS for DMS endpoints for Redis OSS to ensure encrypted communication during data migration.", - "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Redis.html#CHAP_Target.Redis.EndpointSettings" + "Text": "Enable **TLS** on Redis OSS endpoints (e.g., `ssl-encryption`) and require server certificate validation. Prohibit plaintext connections, prefer private networking, and enforce **least privilege** for DMS roles to strengthen **defense in depth**.", + "Url": "https://hub.prowler.com/check/dms_endpoint_redis_in_transit_encryption_enabled" } }, - "Categories": [], + "Categories": [ + "encryption" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.metadata.json index 3392ac2d64..881a4423ce 100644 --- a/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.metadata.json @@ -1,32 +1,40 @@ { "Provider": "aws", "CheckID": "dms_endpoint_ssl_enabled", - "CheckTitle": "Ensure SSL mode is enabled in DMS endpoint", - "CheckType": ["Effects", "Data Exposure"], + "CheckTitle": "DMS endpoint has SSL enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure" + ], "ServiceName": "dms", - "SubServiceName": "endpoint", - "ResourceIdTemplate": "arn:partition:dms:region:account-id:endpoint:resource-id", + "SubServiceName": "", + "ResourceIdTemplate": "", "Severity": "high", "ResourceType": "AwsDmsEndpoint", - "Description": "This check ensures that SSL mode is enabled for all AWS Database Migration Service (DMS) endpoints. Enabling SSL provides encryption in transit for data transferred through these endpoints.", - "Risk": "Without SSL enabled, data transferred through DMS endpoints is not encrypted, potentially exposing sensitive information to unauthorized access or interception during transit.", - "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Security.SSL.html", + "Description": "**AWS DMS endpoints** have their SSL/TLS mode inspected; any value other than `none` denotes encrypted connections between the replication instance and databases.\n\nSupported modes include `require`, `verify-ca`, and `verify-full`.", + "Risk": "Without TLS, data in transit can be read or altered, affecting:\n- **Confidentiality** via packet sniffing and credential leakage\n- **Integrity** through **MITM** tampering of migration streams\n- **Availability** from session hijack or task disruption", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://aws.amazon.com/blogs/database/configuring-ssl-encryption-on-oracle-and-postgresql-endpoints-in-aws-dms/", + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Security.SSL.html", + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-9" + ], "Remediation": { - "Code": { - "CLI": "aws dms modify-endpoint --endpoint-arn --ssl-mode require", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-9", - "Terraform": "" - }, - "Recommendation": { - "Text": "Enable SSL mode for all DMS endpoints. Use 'require' as the minimum SSL mode, and consider using 'verify-ca' or 'verify-full' for higher security.", - "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Security.SSL.html" - } + "Code": { + "CLI": "aws dms modify-endpoint --endpoint-arn --ssl-mode require", + "NativeIaC": "```yaml\n# CloudFormation: Set SSL on a DMS endpoint\nResources:\n :\n Type: AWS::DMS::Endpoint\n Properties:\n EndpointIdentifier: \n EndpointType: source\n EngineName: sqlserver\n ServerName: \n Port: 1433\n Username: \n Password: \n SslMode: require # CRITICAL: enables SSL (not \"none\"), fixing the finding\n```", + "Other": "1. In the AWS DMS console, go to Endpoints\n2. Select the non-compliant endpoint and choose Modify\n3. Set SSL mode to Require (or Verify-ca/Verify-full if required by your engine and certificate is available)\n4. If Verify-ca/Verify-full is selected, choose the appropriate CA certificate\n5. Save changes, then Test connection to confirm", + "Terraform": "```hcl\n# Terraform: Set SSL on a DMS endpoint\nresource \"aws_dms_endpoint\" \"\" {\n endpoint_id = \"\"\n endpoint_type = \"source\"\n engine_name = \"sqlserver\"\n server_name = \"\"\n port = 1433\n username = \"\"\n password = \"\"\n\n ssl_mode = \"require\" # CRITICAL: enables SSL (not \"none\"), fixing the finding\n}\n```" + }, + "Recommendation": { + "Text": "Configure endpoints to use SSL/TLS at least `require`; prefer `verify-ca` or `verify-full` where supported. Manage trusted CA material and rotate regularly. Apply **defense in depth** with private connectivity and strict IAM, and enforce this posture via policy-as-code and continuous validation.", + "Url": "https://hub.prowler.com/check/dms_endpoint_ssl_enabled" + } }, "Categories": [ - "encryption" + "encryption" ], "DependsOn": [], "RelatedTo": [], "Notes": "" -} \ No newline at end of file +} diff --git a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json index 19c2686130..44bbfe4374 100644 --- a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json @@ -1,29 +1,39 @@ { "Provider": "aws", "CheckID": "dms_instance_minor_version_upgrade_enabled", - "CheckTitle": "Ensure DMS instances have auto minor version upgrade enabled.", - "CheckType": [], + "CheckTitle": "DMS replication instance has auto minor version upgrade enabled", + "CheckType": [ + "Software and Configuration Checks/Patch Management", + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" + ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsReplicationInstance", - "Description": "Ensure DMS instances have auto minor version upgrade enabled.", - "Risk": "Ensure that your Amazon Database Migration Service (DMS) replication instances have the Auto Minor Version Upgrade feature enabled in order to receive automatically minor engine upgrades.", - "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-6", + "Description": "**AWS DMS replication instances** are evaluated for the `auto_minor_version_upgrade` setting to confirm **automatic minor engine updates** are enabled during the maintenance window.", + "Risk": "Without **automatic minor upgrades**, DMS engines can miss security patches and fixes, enabling exploitation of known flaws and instability.\n- Confidentiality: exposure via unpatched components\n- Integrity: replication errors or data drift\n- Availability: outages during migration or CDC", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-6", + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/DMS/auto-minor-version-upgrade.html" + ], "Remediation": { "Code": { "CLI": "aws dms modify-replication-instance --region --replication-instance-arn arn:aws:dms:::rep: --auto-minor-version-upgrade --apply-immediately", - "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#", - "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#", - "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#" + "NativeIaC": "```yaml\n# CloudFormation: Enable auto minor version upgrade on a DMS replication instance\nResources:\n :\n Type: AWS::DMS::ReplicationInstance\n Properties:\n ReplicationInstanceIdentifier: \n ReplicationInstanceClass: dms.t3.micro\n AutoMinorVersionUpgrade: true # CRITICAL: turns on automatic minor version upgrades\n```", + "Other": "1. Open the AWS Console and go to Database Migration Service (DMS)\n2. Click Replication instances and select your instance\n3. Choose Actions > Modify\n4. Check Auto minor version upgrade\n5. Select Apply immediately\n6. Click Modify to save", + "Terraform": "```hcl\n# Terraform: Enable auto minor version upgrade on a DMS replication instance\nresource \"aws_dms_replication_instance\" \"\" {\n replication_instance_id = \"\"\n replication_instance_class = \"dms.t3.micro\"\n auto_minor_version_upgrade = true # CRITICAL: turns on automatic minor version upgrades\n}\n```" }, "Recommendation": { - "Text": "Enable auto minor version upgrade for all DMS replication instances.", - "Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-6" + "Text": "Enable `auto_minor_version_upgrade` on all replication instances to maintain **continuous patching**.\n- Set a maintenance window and validate in non-prod\n- Monitor release notes and health metrics\n- Enforce **least privilege** for change control\n- Keep **backups** for rollback", + "Url": "https://hub.prowler.com/check/dms_instance_minor_version_upgrade_enabled" } }, - "Categories": [], + "Categories": [ + "vulnerabilities" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" diff --git a/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.metadata.json index 65d5cd4336..e460711cd7 100644 --- a/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.metadata.json @@ -1,30 +1,37 @@ { "Provider": "aws", "CheckID": "dms_instance_multi_az_enabled", - "CheckTitle": "Ensure DMS instances have multi az enabled.", - "CheckType": [], + "CheckTitle": "DMS replication instance has Multi-AZ enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Denial of Service" + ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsReplicationInstance", - "Description": "Ensure DMS instances have multi az enabled.", - "Risk": "Ensure that your Amazon Database Migration Service (DMS) replication instances are using Multi-AZ deployment configurations to provide High Availability (HA) through automatic failover to standby replicas in the event of a failure such as an Availability Zone (AZ) outage, an internal hardware or network outage, a software failure or in case of a planned maintenance session.", - "RelatedUrl": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", + "Description": "**AWS DMS replication instances** are evaluated for **Multi-AZ** configuration. Instances with `multi_az` enabled are treated as having a cross-AZ standby; those without it are identified as single-AZ.", + "Risk": "Without **Multi-AZ**, a single-AZ failure or maintenance event can halt migrations, causing extended downtime (**availability**) and replication gaps or rollbacks (**integrity**). Tasks may stall, increase cutover risk, and require manual recovery when the replication instance is unavailable.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_ReplicationInstance.html", + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/DMS/multi-az.html" + ], "Remediation": { "Code": { - "CLI": "aws dms modify-replication-instance --region --replication-instance-arn arn:aws:dms:::rep: --multi-az --apply-immediately", - "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", - "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", - "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#" + "CLI": "aws dms modify-replication-instance --replication-instance-arn arn:aws:dms:::rep: --multi-az --apply-immediately", + "NativeIaC": "```yaml\n# CloudFormation: enable Multi-AZ on a DMS replication instance\nResources:\n :\n Type: AWS::DMS::ReplicationInstance\n Properties:\n ReplicationInstanceClass: dms.t3.micro\n MultiAZ: true # Critical: enables Multi-AZ to pass the check\n```", + "Other": "1. Open the AWS DMS console\n2. Go to Replication instances and select your instance\n3. Click Modify\n4. Check Multi-AZ\n5. Check Apply immediately\n6. Click Modify to save", + "Terraform": "```hcl\n# Enable Multi-AZ on a DMS replication instance\nresource \"aws_dms_replication_instance\" \"\" {\n replication_instance_id = \"\"\n replication_instance_class = \"dms.t3.micro\"\n multi_az = true # Critical: enables Multi-AZ to pass the check\n}\n```" }, "Recommendation": { - "Text": "Enable multi az for all DMS replication instances.", - "Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#" + "Text": "Enable **Multi-AZ** (set `multi_az` to `true`) on DMS replication instances that handle production or time-sensitive migrations to ensure redundancy and automatic failover.\n\nApply HA principles: distribute across AZs, test failover, monitor health, and plan maintenance to minimize impact.", + "Url": "https://hub.prowler.com/check/dms_instance_multi_az_enabled" } }, "Categories": [ - "redundancy" + "resilience" ], "DependsOn": [], "RelatedTo": [], diff --git a/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json index 7692268a5c..9a7917e3a5 100644 --- a/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json +++ b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json @@ -1,26 +1,37 @@ { "Provider": "aws", "CheckID": "dms_instance_no_public_access", - "CheckTitle": "Ensure DMS instances are not publicly accessible.", - "CheckType": [], + "CheckTitle": "DMS replication instance is not publicly exposed to the Internet", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark", + "TTPs/Initial Access" + ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "ResourceIdTemplate": "", "Severity": "critical", "ResourceType": "AwsDmsReplicationInstance", - "Description": "Ensure DMS instances are not publicly accessible.", - "Risk": "Ensure that your Amazon Database Migration Service (DMS) are not publicly accessible from the Internet in order to avoid exposing private data and minimize security risks. A DMS replication instance should have a private IP address and the Publicly Accessible feature disabled when both the source and the target databases are in the same network that is connected to the instance's VPC through a VPN, VPC peering connection, or using an AWS Direct Connect dedicated connection.", - "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-1", + "Description": "**AWS DMS replication instances** are evaluated for **public exposure**. Exposure is identified when `PubliclyAccessible` is enabled and an attached security group allows inbound traffic from any address. Private or allowlisted instances are not considered exposed.", + "Risk": "Publicly reachable replication instances threaten:\n- Confidentiality: migration data and credentials can be intercepted or exfiltrated.\n- Integrity: attackers may alter tasks or inject records.\n- Availability: abuse or DDoS can stall replication and delay cutovers.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-1", + "https://docs.aws.amazon.com/amazonq/detector-library/terraform/restrict-public-access-dms-terraform/", + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/DMS/publicly-accessible.html", + "https://support.icompaas.com/support/solutions/articles/62000233448-ensure-dms-instances-are-not-publicly-accessible" + ], "Remediation": { "Code": { "CLI": "", - "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#", - "Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#", - "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#" + "NativeIaC": "```yaml\n# CloudFormation: DMS instance not publicly accessible\nResources:\n :\n Type: AWS::DMS::ReplicationInstance\n Properties:\n ReplicationInstanceClass: dms.t3.micro\n PubliclyAccessible: false # Critical: disables public access to prevent Internet exposure\n```", + "Other": "1. In the AWS Console, open Database Migration Service > Replication instances and select the instance\n2. In Details > Networking, click each attached Security Group ID to open it in the EC2 console\n3. In Inbound rules, delete any rule with Source 0.0.0.0/0 or ::/0\n4. Save rules for each security group", + "Terraform": "```hcl\n# DMS instance not publicly accessible\nresource \"aws_dms_replication_instance\" \"\" {\n replication_instance_id = \"\"\n replication_instance_class = \"dms.t3.micro\"\n publicly_accessible = false # Critical: disables public access to prevent Internet exposure\n}\n```" }, "Recommendation": { - "Text": "Restrict DMS Replication instances security groups to only required IPs, or re-create these instances that is only accessible privately.", - "Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-1" + "Text": "Adopt a **private-only** design:\n- Disable `PubliclyAccessible`; place instances in private subnets.\n- Enforce **least privilege** security groups (no `0.0.0.0/0`); allow only required sources/ports.\n- Provide access via **VPN**, peering, or Direct Connect.\n- Layer controls (ACLs, monitoring) and restrict IAM to necessary actions.", + "Url": "https://hub.prowler.com/check/dms_instance_no_public_access" } }, "Categories": [ diff --git a/prowler/providers/aws/services/dms/dms_replication_task_source_logging_enabled/dms_replication_task_source_logging_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_replication_task_source_logging_enabled/dms_replication_task_source_logging_enabled.metadata.json index 981a51c73b..43c55ab39d 100644 --- a/prowler/providers/aws/services/dms/dms_replication_task_source_logging_enabled/dms_replication_task_source_logging_enabled.metadata.json +++ b/prowler/providers/aws/services/dms/dms_replication_task_source_logging_enabled/dms_replication_task_source_logging_enabled.metadata.json @@ -1,31 +1,39 @@ { "Provider": "aws", "CheckID": "dms_replication_task_source_logging_enabled", - "CheckTitle": "Check if DMS replication tasks for the source database have logging enabled.", + "CheckTitle": "DMS replication task has logging enabled and SOURCE_CAPTURE and SOURCE_UNLOAD components set to at least Default severity", "CheckType": [ - "Software and Configuration Checks/AWS Security Best Practices" + "Software and Configuration Checks/AWS Security Best Practices", + "TTPs/Defense Evasion" ], "ServiceName": "dms", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:dms:region:account-id:task/task-id", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "AwsDmsReplicationTask", - "Description": "This control checks whether logging is enabled with the minimum severity level of LOGGER_SEVERITY_DEFAULT for DMS replication tasks SOURCE_CAPTURE and SOURCE_UNLOAD. The control fails if logging isn't enabled for these tasks or if the minimum severity level is less than LOGGER_SEVERITY_DEFAULT.", - "Risk": "Without logging enabled, issues in data migration may go undetected, affecting the integrity and compliance of replicated data.", - "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Monitoring.html#CHAP_Monitoring.ManagingLogs", + "Description": "**AWS DMS replication tasks** have **logging enabled** and configure `SOURCE_CAPTURE` and `SOURCE_UNLOAD` with severity at least `LOGGER_SEVERITY_DEFAULT` (or higher: `LOGGER_SEVERITY_DEBUG`, `LOGGER_SEVERITY_DETAILED_DEBUG`).", + "Risk": "Missing or low-severity source logs hinder visibility into **CDC** and full-load activity, risking undetected errors, stalls, or tampering. This can cause silent **data drift**, broken lineage, and failed recoveries, undermining **integrity** and **availability** and weakening auditability during investigations.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Monitoring.html", + "https://repost.aws/knowledge-center/dms-debug-logging", + "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-8" + ], "Remediation": { "Code": { - "CLI": "aws dms modify-replication-task --replication-task-arn --task-settings '{\"Logging\":{\"EnableLogging\":true,\"LogComponents\":[{\"Id\":\"SOURCE_CAPTURE\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"SOURCE_UNLOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}]}}'", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-8", - "Terraform": "" + "CLI": "aws dms modify-replication-task --replication-task-arn --replication-task-settings '{\"Logging\":{\"EnableLogging\":true,\"LogComponents\":[{\"Id\":\"SOURCE_CAPTURE\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"SOURCE_UNLOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}]}}'", + "NativeIaC": "```yaml\n# CloudFormation: enable DMS source logging at minimum DEFAULT severity\nResources:\n :\n Type: AWS::DMS::ReplicationTask\n Properties:\n ReplicationInstanceArn: \n SourceEndpointArn: \n TargetEndpointArn: \n MigrationType: full-load\n TableMappings: '{\"rules\":[]}'\n # Critical: Enables logging and sets SOURCE components to at least DEFAULT\n ReplicationTaskSettings: |\n {\n \"Logging\": {\n \"EnableLogging\": true,\n \"LogComponents\": [\n {\"Id\": \"SOURCE_CAPTURE\", \"Severity\": \"LOGGER_SEVERITY_DEFAULT\"},\n {\"Id\": \"SOURCE_UNLOAD\", \"Severity\": \"LOGGER_SEVERITY_DEFAULT\"}\n ]\n }\n }\n```", + "Other": "1. In the AWS console, go to Database Migration Service > Database migration tasks\n2. Select the task and choose Modify\n3. Click Modify task logging\n4. Turn on Enable logging\n5. For SOURCE_CAPTURE and SOURCE_UNLOAD, set Severity to Default (or higher)\n6. Save/Modify to apply", + "Terraform": "```hcl\n# Enable DMS source logging at minimum DEFAULT severity\nresource \"aws_dms_replication_task\" \"\" {\n replication_instance_arn = \"\"\n source_endpoint_arn = \"\"\n target_endpoint_arn = \"\"\n migration_type = \"full-load\"\n table_mappings = \"{\\\"rules\\\":[]}\"\n\n # Critical: Enables logging and sets SOURCE components to at least DEFAULT\n replication_task_settings = < --task-settings '{\"Logging\":{\"EnableLogging\":true,\"LogComponents\":[{\"Id\":\"TARGET_APPLY\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_LOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}]}}'", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-7", - "Terraform": "" + "CLI": "aws dms modify-replication-task --replication-task-arn --replication-task-settings '{\"Logging\":{\"EnableLogging\":true,\"LogComponents\":[{\"Id\":\"TARGET_APPLY\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_LOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}]}}'", + "NativeIaC": "```yaml\n# CloudFormation: enable DMS task logging for target components\nResources:\n :\n Type: AWS::DMS::ReplicationTask\n Properties:\n ReplicationInstanceArn: \n SourceEndpointArn: \n TargetEndpointArn: \n MigrationType: full-load\n TableMappings: |\n {\"rules\":[{\"rule-type\":\"selection\",\"rule-id\":\"1\",\"rule-name\":\"1\",\"object-locator\":{\"schema-name\":\"%\",\"table-name\":\"%\"},\"rule-action\":\"include\"}]}\n ReplicationTaskSettings: |\n {\"Logging\":{\"EnableLogging\":true, \"LogComponents\":[\n {\"Id\":\"TARGET_APPLY\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}, # Critical: ensure TARGET_APPLY logging at default\n {\"Id\":\"TARGET_LOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"} # Critical: ensure TARGET_LOAD logging at default\n ]}}\n```", + "Other": "1. Open the AWS DMS console and go to Database migration tasks\n2. Select the replication task and choose Modify\n3. Expand Task settings (JSON) or Logging\n4. Enable CloudWatch logs (EnableLogging = true)\n5. Set log components:\n - TARGET_APPLY severity: DEFAULT\n - TARGET_LOAD severity: DEFAULT\n6. Save changes (Modify task), then rerun the task if required", + "Terraform": "```hcl\n# Enable DMS task logging for target components\nresource \"aws_dms_replication_task\" \"\" {\n replication_task_id = \"\"\n replication_instance_arn = \"\"\n source_endpoint_arn = \"\"\n target_endpoint_arn = \"\"\n migration_type = \"full-load\"\n table_mappings = jsonencode({ rules = [{\n \"rule-type\" : \"selection\", \"rule-id\" : \"1\", \"rule-name\" : \"1\",\n \"object-locator\" : { \"schema-name\" : \"%\", \"table-name\" : \"%\" },\n \"rule-action\" : \"include\"\n }]} )\n\n # Critical: enables logging and sets TARGET_APPLY and TARGET_LOAD to minimum required severity\n replication_task_settings = jsonencode({\n Logging = {\n EnableLogging = true\n LogComponents = [\n { Id = \"TARGET_APPLY\", Severity = \"LOGGER_SEVERITY_DEFAULT\" },\n { Id = \"TARGET_LOAD\", Severity = \"LOGGER_SEVERITY_DEFAULT\" }\n ]\n }\n })\n}\n```" }, "Recommendation": { - "Text": "Enable logging for target database DMS replication tasks with a minimum severity level of LOGGER_SEVERITY_DEFAULT.", - "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TaskSettings.Logging.html" + "Text": "Enable and maintain **CloudWatch logging** at `LOGGER_SEVERITY_DEFAULT` or higher for target components:\n- Configure `TARGET_APPLY` and `TARGET_LOAD`\n- Enforce least-privilege log access\n- Monitor logs/alerts for anomalies\n- Standardize task settings and validate data for **defense in depth**", + "Url": "https://hub.prowler.com/check/dms_replication_task_target_logging_enabled" } }, - "Categories": [], + "Categories": [ + "logging" + ], "DependsOn": [], "RelatedTo": [], "Notes": "" From 9c6c007f731a32f82759e6dd0bfe0b994c3c3118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 21 Oct 2025 16:45:05 +0200 Subject: [PATCH 26/57] fix(mcp): add missing argument to health check (#8967) --- mcp_server/prowler_mcp_server/server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mcp_server/prowler_mcp_server/server.py b/mcp_server/prowler_mcp_server/server.py index 044d2b5a55..9f36befb73 100644 --- a/mcp_server/prowler_mcp_server/server.py +++ b/mcp_server/prowler_mcp_server/server.py @@ -2,6 +2,7 @@ import asyncio import os from fastmcp import FastMCP +from prowler_mcp_server import __version__ from prowler_mcp_server.lib.logger import logger from starlette.responses import JSONResponse @@ -54,9 +55,11 @@ async def setup_main_server(): # Add health check endpoint @prowler_mcp_server.custom_route("/health", methods=["GET"]) -async def health_check() -> JSONResponse: +async def health_check(request) -> JSONResponse: """Health check endpoint.""" - return JSONResponse({"status": "healthy", "service": "prowler-mcp-server"}) + return JSONResponse( + {"status": "healthy", "service": "prowler-mcp-server", "version": __version__} + ) # Get or create the event loop From ab06a09173ccb2301e395303b90e3e00d7e982b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:10:48 +0200 Subject: [PATCH 27/57] chore(api): improve pull request action (#8963) --- .../actions/setup-python-poetry/action.yml | 71 +++++ .github/actions/trivy-scan/action.yml | 152 +++++++++ .github/scripts/trivy-pr-comment.js | 102 ++++++ .github/workflows/api-pull-request.yml | 296 +++++++++--------- 4 files changed, 468 insertions(+), 153 deletions(-) create mode 100644 .github/actions/setup-python-poetry/action.yml create mode 100644 .github/actions/trivy-scan/action.yml create mode 100644 .github/scripts/trivy-pr-comment.js diff --git a/.github/actions/setup-python-poetry/action.yml b/.github/actions/setup-python-poetry/action.yml new file mode 100644 index 0000000000..3b774262f9 --- /dev/null +++ b/.github/actions/setup-python-poetry/action.yml @@ -0,0 +1,71 @@ +name: 'Setup Python with Poetry' +description: 'Setup Python environment with Poetry and install dependencies' +author: 'Prowler' + +inputs: + python-version: + description: 'Python version to use' + required: true + working-directory: + description: 'Working directory for Poetry' + required: false + default: '.' + poetry-version: + description: 'Poetry version to install' + required: false + default: '2.1.1' + install-dependencies: + description: 'Install Python dependencies with Poetry' + required: false + default: 'true' + +runs: + using: 'composite' + steps: + - name: Replace @master with current branch in pyproject.toml + if: github.event_name == 'pull_request' && github.base_ref == 'master' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" + echo "Using branch: $BRANCH_NAME" + sed -i "s|@master|@$BRANCH_NAME|g" pyproject.toml + + - name: Install poetry + shell: bash + run: | + python -m pip install --upgrade pip + pipx install poetry==${{ inputs.poetry-version }} + + - name: Update SDK resolved_reference to latest commit + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha') + echo "Latest commit hash: $LATEST_COMMIT" + sed -i '/url = "https:\/\/github\.com\/prowler-cloud\/prowler\.git"/,/resolved_reference = / { + s/resolved_reference = "[a-f0-9]\{40\}"/resolved_reference = "'"$LATEST_COMMIT"'"/ + }' poetry.lock + echo "Updated resolved_reference:" + grep -A2 -B2 "resolved_reference" poetry.lock + + - name: Update poetry.lock + shell: bash + working-directory: ${{ inputs.working-directory }} + run: poetry lock + + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + with: + python-version: ${{ inputs.python-version }} + cache: 'poetry' + cache-dependency-path: ${{ inputs.working-directory }}/poetry.lock + + - name: Install Python dependencies + if: inputs.install-dependencies == 'true' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + poetry install --no-root + poetry run pip list diff --git a/.github/actions/trivy-scan/action.yml b/.github/actions/trivy-scan/action.yml new file mode 100644 index 0000000000..91c4c4d332 --- /dev/null +++ b/.github/actions/trivy-scan/action.yml @@ -0,0 +1,152 @@ +name: 'Container Security Scan with Trivy' +description: 'Scans container images for vulnerabilities using Trivy and reports results' +author: 'Prowler' + +inputs: + image-name: + description: 'Container image name to scan' + required: true + image-tag: + description: 'Container image tag to scan' + required: true + default: ${{ github.sha }} + severity: + description: 'Severities to scan for (comma-separated)' + required: false + default: 'CRITICAL,HIGH,MEDIUM,LOW' + fail-on-critical: + description: 'Fail the build if critical vulnerabilities are found' + required: false + default: 'false' + upload-sarif: + description: 'Upload results to GitHub Security tab' + required: false + default: 'true' + create-pr-comment: + description: 'Create a comment on the PR with scan results' + required: false + default: 'true' + artifact-retention-days: + description: 'Days to retain the Trivy report artifact' + required: false + default: '2' + +outputs: + critical-count: + description: 'Number of critical vulnerabilities found' + value: ${{ steps.security-check.outputs.critical }} + high-count: + description: 'Number of high vulnerabilities found' + value: ${{ steps.security-check.outputs.high }} + total-count: + description: 'Total number of vulnerabilities found' + value: ${{ steps.security-check.outputs.total }} + +runs: + using: 'composite' + steps: + - name: Run Trivy vulnerability scan (SARIF) + if: inputs.upload-sarif == 'true' + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1 + with: + image-ref: ${{ inputs.image-name }}:${{ inputs.image-tag }} + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + exit-code: '0' + + - name: Upload Trivy results to GitHub Security tab + if: inputs.upload-sarif == 'true' + uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + with: + sarif_file: 'trivy-results.sarif' + category: 'trivy-container' + + - name: Run Trivy vulnerability scan (JSON) + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1 + with: + image-ref: ${{ inputs.image-name }}:${{ inputs.image-tag }} + format: 'json' + output: 'trivy-report.json' + severity: ${{ inputs.severity }} + exit-code: '0' + + - name: Upload Trivy report artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: trivy-scan-report-${{ inputs.image-name }} + path: trivy-report.json + retention-days: ${{ inputs.artifact-retention-days }} + + - name: Generate security summary + id: security-check + shell: bash + run: | + CRITICAL=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' trivy-report.json) + HIGH=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="HIGH")] | length' trivy-report.json) + TOTAL=$(jq '[.Results[]?.Vulnerabilities[]?] | length' trivy-report.json) + + echo "critical=$CRITICAL" >> $GITHUB_OUTPUT + echo "high=$HIGH" >> $GITHUB_OUTPUT + echo "total=$TOTAL" >> $GITHUB_OUTPUT + + echo "### 🔒 Container Security Scan" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Image:** \`${{ inputs.image-name }}:${{ inputs.image-tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- 🔴 Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY + echo "- 🟠 High: $HIGH" >> $GITHUB_STEP_SUMMARY + echo "- **Total**: $TOTAL" >> $GITHUB_STEP_SUMMARY + + - name: Comment scan results on PR + if: inputs.create-pr-comment == 'true' && github.event_name == 'pull_request' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + IMAGE_NAME: ${{ inputs.image-name }} + GITHUB_SHA: ${{ inputs.image-tag }} + SEVERITY: ${{ inputs.severity }} + with: + script: | + const comment = require('./.github/scripts/trivy-pr-comment.js'); + + // Unique identifier to find our comment + const marker = ''; + const body = marker + '\n' + comment; + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const existingComment = comments.find(c => c.body?.includes(marker)); + + if (existingComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: body + }); + console.log('✅ Updated existing Trivy scan comment'); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + console.log('✅ Created new Trivy scan comment'); + } + + - name: Check for critical vulnerabilities + if: inputs.fail-on-critical == 'true' && steps.security-check.outputs.critical != '0' + shell: bash + run: | + echo "::error::Found ${{ steps.security-check.outputs.critical }} critical vulnerabilities" + echo "::warning::Please update packages or use a different base image" + exit 1 diff --git a/.github/scripts/trivy-pr-comment.js b/.github/scripts/trivy-pr-comment.js new file mode 100644 index 0000000000..eb725b1af1 --- /dev/null +++ b/.github/scripts/trivy-pr-comment.js @@ -0,0 +1,102 @@ +const fs = require('fs'); + +// Configuration from environment variables +const REPORT_FILE = process.env.TRIVY_REPORT_FILE || 'trivy-report.json'; +const IMAGE_NAME = process.env.IMAGE_NAME || 'container-image'; +const GITHUB_SHA = process.env.GITHUB_SHA || 'unknown'; +const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || ''; +const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID || ''; +const SEVERITY = process.env.SEVERITY || 'CRITICAL,HIGH,MEDIUM,LOW'; + +// Parse severities to scan +const scannedSeverities = SEVERITY.split(',').map(s => s.trim()); + +// Read and parse the Trivy report +const report = JSON.parse(fs.readFileSync(REPORT_FILE, 'utf-8')); + +let vulnCount = 0; +let vulnsByType = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }; +let affectedPackages = new Set(); + +if (report.Results && Array.isArray(report.Results)) { + for (const result of report.Results) { + if (result.Vulnerabilities && Array.isArray(result.Vulnerabilities)) { + for (const vuln of result.Vulnerabilities) { + vulnCount++; + if (vulnsByType[vuln.Severity] !== undefined) { + vulnsByType[vuln.Severity]++; + } + if (vuln.PkgName) { + affectedPackages.add(vuln.PkgName); + } + } + } + } +} + +const shortSha = GITHUB_SHA.substring(0, 7); +const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; + +// Severity icons and labels +const severityConfig = { + CRITICAL: { icon: '🔴', label: 'Critical' }, + HIGH: { icon: '🟠', label: 'High' }, + MEDIUM: { icon: '🟡', label: 'Medium' }, + LOW: { icon: '🔵', label: 'Low' } +}; + +let comment = '## 🔒 Container Security Scan\n\n'; +comment += `**Image:** \`${IMAGE_NAME}:${shortSha}\`\n`; +comment += `**Last scan:** ${timestamp}\n\n`; + +if (vulnCount === 0) { + comment += '### ✅ No Vulnerabilities Detected\n\n'; + comment += 'The container image passed all security checks. No known CVEs were found.\n'; +} else { + comment += '### 📊 Vulnerability Summary\n\n'; + comment += '| Severity | Count |\n'; + comment += '|----------|-------|\n'; + + // Only show severities that were scanned + for (const severity of scannedSeverities) { + const config = severityConfig[severity]; + const count = vulnsByType[severity] || 0; + const isBold = (severity === 'CRITICAL' || severity === 'HIGH') && count > 0; + const countDisplay = isBold ? `**${count}**` : count; + comment += `| ${config.icon} ${config.label} | ${countDisplay} |\n`; + } + + comment += `| **Total** | **${vulnCount}** |\n\n`; + + if (affectedPackages.size > 0) { + comment += `**${affectedPackages.size}** package(s) affected\n\n`; + } + + if (vulnsByType.CRITICAL > 0) { + comment += '### ⚠️ Action Required\n\n'; + comment += '**Critical severity vulnerabilities detected.** These should be addressed before merging:\n'; + comment += '- Review the detailed scan results\n'; + comment += '- Update affected packages to patched versions\n'; + comment += '- Consider using a different base image if updates are unavailable\n\n'; + } else if (vulnsByType.HIGH > 0) { + comment += '### ⚠️ Attention Needed\n\n'; + comment += '**High severity vulnerabilities found.** Please review and plan remediation:\n'; + comment += '- Assess the risk and exploitability\n'; + comment += '- Prioritize updates in the next maintenance cycle\n\n'; + } else { + comment += '### ℹ️ Review Recommended\n\n'; + comment += 'Medium/Low severity vulnerabilities found. Consider addressing during regular maintenance.\n\n'; + } +} + +comment += '---\n'; +comment += '📋 **Resources:**\n'; + +if (GITHUB_REPOSITORY && GITHUB_RUN_ID) { + comment += `- [Download full report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) (see artifacts)\n`; +} + +comment += '- [View in Security tab](https://github.com/' + (GITHUB_REPOSITORY || 'repository') + '/security/code-scanning)\n'; +comment += '- Scanned with [Trivy](https://github.com/aquasecurity/trivy)\n'; + +module.exports = comment; diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index e899af8f32..58f7cdc38d 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -1,20 +1,30 @@ -name: API - Pull Request +name: 'API: Pull Request' on: push: branches: - - "master" - - "v5.*" + - 'master' + - 'v5.*' paths: - - ".github/workflows/api-pull-request.yml" - - "api/**" + - '.github/workflows/api-pull-request.yml' + - 'api/**' + - '!api/docs/**' + - '!api/README.md' + - '!api/CHANGELOG.md' pull_request: branches: - - "master" - - "v5.*" + - 'master' + - 'v5.*' paths: - - ".github/workflows/api-pull-request.yml" - - "api/**" + - '.github/workflows/api-pull-request.yml' + - 'api/**' + - '!api/docs/**' + - '!api/README.md' + - '!api/CHANGELOG.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: POSTGRES_HOST: localhost @@ -29,21 +39,91 @@ env: VALKEY_DB: 0 API_WORKING_DIR: ./api IMAGE_NAME: prowler-api - IGNORE_FILES: | - api/docs/** - api/README.md - api/CHANGELOG.md jobs: - test: + code-quality: runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read strategy: matrix: - python-version: ["3.12"] + python-version: + - '3.12' + defaults: + run: + working-directory: ./api + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup Python with Poetry + uses: ./.github/actions/setup-python-poetry + with: + python-version: ${{ matrix.python-version }} + working-directory: ./api + + - name: Poetry check + run: poetry check --lock + + - name: Ruff lint + run: poetry run ruff check . --exclude contrib + + - name: Ruff format + run: poetry run ruff format --check . --exclude contrib + + - name: Pylint + run: poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/ + + security-scans: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + strategy: + matrix: + python-version: + - '3.12' + defaults: + run: + working-directory: ./api + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup Python with Poetry + uses: ./.github/actions/setup-python-poetry + with: + python-version: ${{ matrix.python-version }} + working-directory: ./api + + - name: Bandit + run: poetry run bandit -q -lll -x '*_test.py,./contrib/' -r . + + - name: Safety + # 76352, 76353, 77323 come from SDK, but they cannot upgrade it yet. It does not affect API + # TODO: Botocore needs urllib3 1.X so we need to ignore these vulnerabilities 77744,77745. Remove this once we upgrade to urllib3 2.X + run: poetry run safety check --ignore 70612,66963,74429,76352,76353,77323,77744,77745 + + - name: Vulture + run: poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 . + + tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + strategy: + matrix: + python-version: + - '3.12' + defaults: + run: + working-directory: ./api - # Service containers to run with `test` services: - # Label used to access the service container postgres: image: postgres env: @@ -52,7 +132,6 @@ jobs: POSTGRES_USER: ${{ env.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} POSTGRES_DB: ${{ env.POSTGRES_DB }} - # Set health checks to wait until postgres has started ports: - 5432:5432 options: >- @@ -66,7 +145,6 @@ jobs: VALKEY_HOST: ${{ env.VALKEY_HOST }} VALKEY_PORT: ${{ env.VALKEY_PORT }} VALKEY_DB: ${{ env.VALKEY_DB }} - # Set health checks to wait until postgres has started ports: - 6379:6379 options: >- @@ -76,158 +154,70 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Test if changes are in not ignored paths - id: are-non-ignored-files-changed - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 - with: - files: | - api/** - .github/workflows/api-pull-request.yml - files_ignore: ${{ env.IGNORE_FILES }} - - - name: Replace @master with current branch in pyproject.toml - Only for pull requests to `master` - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' && github.event_name == 'pull_request' && github.base_ref == 'master' - run: | - BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" - echo "Using branch: $BRANCH_NAME" - sed -i "s|@master|@$BRANCH_NAME|g" pyproject.toml - - - name: Install poetry - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - python -m pip install --upgrade pip - pipx install poetry==2.1.1 - - - name: Update SDK's poetry.lock resolved_reference to latest commit - Only for push events to `master` - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/master' - run: | - # Get the latest commit hash from the prowler-cloud/prowler repository - LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha') - echo "Latest commit hash: $LATEST_COMMIT" - - # Update the resolved_reference specifically for prowler-cloud/prowler repository - sed -i '/url = "https:\/\/github\.com\/prowler-cloud\/prowler\.git"/,/resolved_reference = / { - s/resolved_reference = "[a-f0-9]\{40\}"/resolved_reference = "'"$LATEST_COMMIT"'"/ - }' poetry.lock - - # Verify the change was made - echo "Updated resolved_reference:" - grep -A2 -B2 "resolved_reference" poetry.lock - - - name: Update poetry.lock - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry lock - - - name: Set up Python ${{ matrix.python-version }} - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - name: Setup Python with Poetry + uses: ./.github/actions/setup-python-poetry with: python-version: ${{ matrix.python-version }} - cache: "poetry" + working-directory: ./api - - name: Install dependencies - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry install --no-root - poetry run pip list - VERSION=$(curl --silent "https://api.github.com/repos/hadolint/hadolint/releases/latest" | \ - grep '"tag_name":' | \ - sed -E 's/.*"v([^"]+)".*/\1/' \ - ) && curl -L -o /tmp/hadolint "https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64" \ - && chmod +x /tmp/hadolint - - - name: Poetry check - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry check --lock - - - name: Lint with ruff - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run ruff check . --exclude contrib - - - name: Check Format with ruff - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run ruff format --check . --exclude contrib - - - name: Lint with pylint - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/ - - - name: Bandit - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run bandit -q -lll -x '*_test.py,./contrib/' -r . - - - name: Safety - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - # 76352, 76353, 77323 come from SDK, but they cannot upgrade it yet. It does not affect API - # TODO: Botocore needs urllib3 1.X so we need to ignore these vulnerabilities 77744,77745. Remove this once we upgrade to urllib3 2.X - run: | - poetry run safety check --ignore 70612,66963,74429,76352,76353,77323,77744,77745 - - - name: Vulture - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 . - - - name: Hadolint - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - /tmp/hadolint Dockerfile --ignore=DL3013 - - - name: Test with pytest - working-directory: ./api - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' - run: | - poetry run pytest --cov=./src/backend --cov-report=xml src/backend + - name: Run tests with pytest + run: poetry run pytest --cov=./src/backend --cov-report=xml src/backend - name: Upload coverage reports to Codecov - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: flags: api - test-container-build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Test if changes are in not ignored paths - id: are-non-ignored-files-changed - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 + dockerfile-lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Lint Dockerfile with Hadolint + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 with: - files: api/** - files_ignore: ${{ env.IGNORE_FILES }} + dockerfile: api/Dockerfile + ignore: DL3013 + + container-build-and-scan: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + security-events: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Set up Docker Buildx - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - - name: Build Container - if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' + + - name: Build container uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: ${{ env.API_WORKING_DIR }} push: false - tags: ${{ env.IMAGE_NAME }}:latest - outputs: type=docker + load: true + tags: ${{ env.IMAGE_NAME }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Scan container with Trivy + uses: ./.github/actions/trivy-scan + with: + image-name: ${{ env.IMAGE_NAME }} + image-tag: ${{ github.sha }} + fail-on-critical: 'false' + severity: 'CRITICAL' From c6cb4e48149ca151eb532323723e0b69794e3694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:14:40 +0200 Subject: [PATCH 28/57] chore(github): improve backport action (#8968) --- .github/workflows/backport.yml | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 2aa0a49c09..4fd1347f44 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,28 +1,35 @@ -name: Prowler - Automatic Backport +name: 'Tools: Backport' on: pull_request_target: - branches: ['master'] - types: ['labeled', 'closed'] + branches: + - 'master' + types: + - 'labeled' + - 'closed' + paths: + - '.github/workflows/backport.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: false env: - # The prefix of the label that triggers the backport must not contain the branch name - # so, for example, if the branch is 'master', the label should be 'backport-to-' BACKPORT_LABEL_PREFIX: backport-to- BACKPORT_LABEL_IGNORE: was-backported jobs: backport: - name: Backport PR if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport')) && !(contains(github.event.pull_request.labels.*.name, 'was-backported')) runs-on: ubuntu-latest + timeout-minutes: 15 permissions: - id-token: write - pull-requests: write contents: write + pull-requests: write + steps: - name: Check labels - id: preview_label_check + id: label_check uses: agilepathway/label-checker@c3d16ad512e7cea5961df85ff2486bb774caf3c5 # v1.6.65 with: allow_failure: true @@ -31,17 +38,17 @@ jobs: none_of: ${{ env.BACKPORT_LABEL_IGNORE }} repo_token: ${{ secrets.GITHUB_TOKEN }} - - name: Backport Action - if: steps.preview_label_check.outputs.label_check == 'success' + - name: Backport PR + if: steps.label_check.outputs.label_check == 'success' uses: sorenlouv/backport-github-action@ad888e978060bc1b2798690dd9d03c4036560947 # v9.5.1 with: github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }} - - name: Info log - if: ${{ success() && steps.preview_label_check.outputs.label_check == 'success' }} + - name: Display backport info log + if: success() && steps.label_check.outputs.label_check == 'success' run: cat ~/.backport/backport.info.log - - name: Debug log - if: ${{ failure() && steps.preview_label_check.outputs.label_check == 'success' }} + - name: Display backport debug log + if: failure() && steps.label_check.outputs.label_check == 'success' run: cat ~/.backport/backport.debug.log From 5d5c10906785f8ab2dc7d1ce353e99bc45bc5d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Tue, 21 Oct 2025 17:40:19 +0200 Subject: [PATCH 29/57] chore(aws): enhance metadata for `dlm` service (#8860) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 1 + ...shot_lifecycle_policy_exists.metadata.json | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 44d5ffef64..712c53ac25 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -37,6 +37,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update AWS Backup service metadata to new format [(#8826)](https://github.com/prowler-cloud/prowler/pull/8826) - Update AWS CloudFormation service metadata to new format [(#8828)](https://github.com/prowler-cloud/prowler/pull/8828) - Update AWS Lambda service metadata to new format [(#8825)](https://github.com/prowler-cloud/prowler/pull/8825) +- Update AWS DLM service metadata to new format [(#8860)](https://github.com/prowler-cloud/prowler/pull/8860) - Update AWS DMS service metadata to new format [(#8861)](https://github.com/prowler-cloud/prowler/pull/8861) - Update AWS Directory Service service metadata to new format [(#8859)](https://github.com/prowler-cloud/prowler/pull/8859) - Update AWS CloudFront service metadata to new format [(#8829)](https://github.com/prowler-cloud/prowler/pull/8829) diff --git a/prowler/providers/aws/services/dlm/dlm_ebs_snapshot_lifecycle_policy_exists/dlm_ebs_snapshot_lifecycle_policy_exists.metadata.json b/prowler/providers/aws/services/dlm/dlm_ebs_snapshot_lifecycle_policy_exists/dlm_ebs_snapshot_lifecycle_policy_exists.metadata.json index 12b55d3f12..db011dacd4 100644 --- a/prowler/providers/aws/services/dlm/dlm_ebs_snapshot_lifecycle_policy_exists/dlm_ebs_snapshot_lifecycle_policy_exists.metadata.json +++ b/prowler/providers/aws/services/dlm/dlm_ebs_snapshot_lifecycle_policy_exists/dlm_ebs_snapshot_lifecycle_policy_exists.metadata.json @@ -1,28 +1,34 @@ { "Provider": "aws", "CheckID": "dlm_ebs_snapshot_lifecycle_policy_exists", - "CheckTitle": "Ensure EBS Snapshot lifecycle policies are defined.", + "CheckTitle": "Region with EBS snapshots has at least one EBS snapshot lifecycle policy defined", "CheckType": [ - "Data Protection" + "Software and Configuration Checks/AWS Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" ], "ServiceName": "dlm", - "SubServiceName": "ebs", - "ResourceIdTemplate": "arn:aws:iam::account-id:resource-id", + "SubServiceName": "", + "ResourceIdTemplate": "", "Severity": "medium", "ResourceType": "Other", - "Description": "Ensure EBS Snapshot lifecycle policies are defined.", - "Risk": "With AWS DLM service, you can manage the lifecycle of your EBS volume snapshots. By automating the EBS volume backup management using lifecycle policies, you can protect your EBS data by enforcing a regular backup schedule, retain backups as required by auditors or internal compliance.", - "RelatedUrl": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snapshot-lifecycle.html#dlm-elements", + "Description": "**EBS snapshots** are expected to be governed by **Data Lifecycle Manager (DLM) policies** in each Region where snapshots exist.\n\nThe evaluation looks for lifecycle policies that automate snapshot creation, retention, and cleanup for those snapshots.", + "Risk": "Without **automated lifecycle policies**, backups become inconsistent and error-prone, reducing availability and weakening recovery objectives. Missing retention rules cause premature deletion or snapshot sprawl, increasing cost and exposing stale data. Lack of cross-Region/account copies limits resilience to regional outages and malicious deletion.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/DLM/ebs-snapshot-automation.html", + "https://repost.aws/articles/ARmYgZmA8MRQi89pWd9D7eFw/how-to-create-a-automate-backup-aws-data-lifecycle-management-using-snapshots", + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snapshot-lifecycle.html#dlm-elements" + ], "Remediation": { "Code": { - "CLI": "aws dlm create-lifecycle-policy --region --execution-role-arn --description --state ENABLED --policy-details file://lifecycle-policy-config.json", - "NativeIaC": "", - "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/DLM/ebs-snapshot-automation.html", - "Terraform": "" + "CLI": "aws dlm create-lifecycle-policy --region --execution-role-arn --description \"\" --state ENABLED --policy-details '{\"PolicyType\":\"EBS_SNAPSHOT_MANAGEMENT\",\"ResourceTypes\":[\"VOLUME\"],\"TargetTags\":[{\"Key\":\"\",\"Value\":\"\"}],\"Schedules\":[{\"CreateRule\":{\"Interval\":24,\"IntervalUnit\":\"HOURS\"},\"RetainRule\":{\"Count\":1}}]}'", + "NativeIaC": "```yaml\n# CloudFormation: minimal EBS snapshot lifecycle policy\nResources:\n :\n Type: AWS::DLM::LifecyclePolicy\n Properties:\n Description: \"\"\n ExecutionRoleArn: \"\"\n State: ENABLED # Critical: enables the policy so it is counted by the check\n PolicyDetails:\n PolicyType: EBS_SNAPSHOT_MANAGEMENT # Critical: creates an EBS snapshot lifecycle policy\n ResourceTypes: [VOLUME]\n TargetTags:\n - Key: \"\" # Critical: selects target volumes by tag\n Value: \"\"\n Schedules:\n - CreateRule:\n Interval: 24\n IntervalUnit: HOURS\n RetainRule:\n Count: 1\n```", + "Other": "1. In the AWS console, switch to the Region that has EBS snapshots\n2. Open EC2 > Lifecycle Manager (DLM) > Create lifecycle policy\n3. Select EBS snapshot policy; Target resource: Volumes\n4. Add Target tags: Key = , Value = \n5. Set Schedule: Create every 24 hours; Retain 1 snapshot\n6. Ensure State is Enabled and click Create policy", + "Terraform": "```hcl\n# Terraform: minimal EBS snapshot lifecycle policy\nresource \"aws_dlm_lifecycle_policy\" \"\" {\n description = \"\"\n execution_role_arn = \"\"\n state = \"ENABLED\" # Critical: enables the policy so it is counted by the check\n\n policy_details {\n policy_type = \"EBS_SNAPSHOT_MANAGEMENT\" # Critical: creates an EBS snapshot lifecycle policy\n resource_types = [\"VOLUME\"]\n target_tags = {\n \"\" = \"\" # Critical: selects target volumes by tag\n }\n schedule {\n create_rule {\n interval = 24\n interval_unit = \"HOURS\"\n }\n retain_rule {\n count = 1\n }\n }\n }\n}\n```" }, "Recommendation": { - "Text": "To use Amazon Data Lifecycle Manager (DLM) service to manage the lifecycle of your EBS volume snapshots, you have to tag your AWS EBS volumes and create data lifecycle policies via Amazon DLM.", - "Url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snapshot-lifecycle.html#dlm-elements" + "Text": "Implement **DLM lifecycle policies** for all volumes that require backup.\n\n- Schedule creations to meet RPO/RTO\n- Define retention to prevent sprawl and enforce least data exposure\n- Use **least privilege** roles and separation of duties\n- Copy snapshots to another Region/account for **defense in depth**\n- Monitor policy health and coverage with tags", + "Url": "https://hub.prowler.com/check/dlm_ebs_snapshot_lifecycle_policy_exists" } }, "Categories": [ From 3eaa21f06fd4451d72a60320c7010aad52432613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:57:04 +0200 Subject: [PATCH 30/57] chore(github): improve backport label action (#8970) --- .github/workflows/create-backport-label.yml | 95 +++++++++++---------- .github/workflows/find-secrets.yml | 2 +- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/.github/workflows/create-backport-label.yml b/.github/workflows/create-backport-label.yml index 3b485ec513..b4308156c7 100644 --- a/.github/workflows/create-backport-label.yml +++ b/.github/workflows/create-backport-label.yml @@ -1,67 +1,70 @@ -name: Prowler - Create Backport Label +name: 'Tools: Backport Label' on: release: - types: [published] + types: + - 'published' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.release.tag_name }} + cancel-in-progress: false + +env: + BACKPORT_LABEL_PREFIX: backport-to- + BACKPORT_LABEL_COLOR: B60205 jobs: - create_label: + create-label: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: - contents: write + contents: read issues: write + steps: - - name: Create backport label + - name: Create backport label for minor releases env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ github.event.release.tag_name }} - OWNER_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - VERSION_ONLY=${RELEASE_TAG#v} # Remove 'v' prefix if present (e.g., v3.2.0 -> 3.2.0) + RELEASE_TAG="${{ github.event.release.tag_name }}" + + if [ -z "$RELEASE_TAG" ]; then + echo "Error: No release tag provided" + exit 1 + fi + + echo "Processing release tag: $RELEASE_TAG" + + # Remove 'v' prefix if present (e.g., v3.2.0 -> 3.2.0) + VERSION_ONLY="${RELEASE_TAG#v}" # Check if it's a minor version (X.Y.0) - if [[ "$VERSION_ONLY" =~ ^[0-9]+\.[0-9]+\.0$ ]]; then - echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is a minor version. Proceeding to create backport label." + if [[ "$VERSION_ONLY" =~ ^([0-9]+)\.([0-9]+)\.0$ ]]; then + echo "Release $RELEASE_TAG (version $VERSION_ONLY) is a minor version. Proceeding to create backport label." - TWO_DIGIT_VERSION=${VERSION_ONLY%.0} # Extract X.Y from X.Y.0 (e.g., 5.6 from 5.6.0) + # Extract X.Y from X.Y.0 (e.g., 5.6 from 5.6.0) + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + TWO_DIGIT_VERSION="${MAJOR}.${MINOR}" - FINAL_LABEL_NAME="backport-to-v${TWO_DIGIT_VERSION}" - FINAL_DESCRIPTION="Backport PR to the v${TWO_DIGIT_VERSION} branch" + LABEL_NAME="${BACKPORT_LABEL_PREFIX}v${TWO_DIGIT_VERSION}" + LABEL_DESC="Backport PR to the v${TWO_DIGIT_VERSION} branch" + LABEL_COLOR="$BACKPORT_LABEL_COLOR" - echo "Effective label name will be: ${FINAL_LABEL_NAME}" - echo "Effective description will be: ${FINAL_DESCRIPTION}" + echo "Label name: $LABEL_NAME" + echo "Label description: $LABEL_DESC" - # Check if the label already exists - STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/repos/${OWNER_REPO}/labels/${FINAL_LABEL_NAME}") - - if [ "${STATUS_CODE}" -eq 200 ]; then - echo "Label '${FINAL_LABEL_NAME}' already exists." - elif [ "${STATUS_CODE}" -eq 404 ]; then - echo "Label '${FINAL_LABEL_NAME}' does not exist. Creating it..." - # Prepare JSON data payload - JSON_DATA=$(printf '{"name":"%s","description":"%s","color":"B60205"}' "${FINAL_LABEL_NAME}" "${FINAL_DESCRIPTION}") - - CREATE_STATUS_CODE=$(curl -s -o /tmp/curl_create_response.json -w "%{http_code}" -X POST \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - --data "${JSON_DATA}" \ - "https://api.github.com/repos/${OWNER_REPO}/labels") - - CREATE_RESPONSE_BODY=$(cat /tmp/curl_create_response.json) - rm -f /tmp/curl_create_response.json - - if [ "$CREATE_STATUS_CODE" -eq 201 ]; then - echo "Label '${FINAL_LABEL_NAME}' created successfully." - else - echo "Error creating label '${FINAL_LABEL_NAME}'. Status: $CREATE_STATUS_CODE" - echo "Response: $CREATE_RESPONSE_BODY" - exit 1 - fi + # Check if label already exists + if gh label list --repo ${{ github.repository }} --limit 1000 | grep -q "^${LABEL_NAME}[[:space:]]"; then + echo "Label '$LABEL_NAME' already exists." else - echo "Error checking for label '${FINAL_LABEL_NAME}'. HTTP Status: ${STATUS_CODE}" - exit 1 + echo "Label '$LABEL_NAME' does not exist. Creating it..." + gh label create "$LABEL_NAME" \ + --description "$LABEL_DESC" \ + --color "$LABEL_COLOR" \ + --repo ${{ github.repository }} + echo "Label '$LABEL_NAME' created successfully." fi else - echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is not a minor version. Skipping backport label creation." - exit 0 + echo "Release $RELEASE_TAG (version $VERSION_ONLY) is not a minor version. Skipping backport label creation." fi diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index e7feaea43a..d1258b3827 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -1,4 +1,4 @@ -name: Prowler - Find secrets +name: 'Tools: TruffleHog' on: pull_request From a3db23af7d8f19946256cd82281425796b979ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:57:29 +0200 Subject: [PATCH 31/57] chore(github): improve conventional commits action (#8969) --- .github/workflows/conventional-commit.yml | 35 ++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/conventional-commit.yml b/.github/workflows/conventional-commit.yml index ecaf651c34..51626518a8 100644 --- a/.github/workflows/conventional-commit.yml +++ b/.github/workflows/conventional-commit.yml @@ -1,24 +1,33 @@ -name: Prowler - Conventional Commit +name: 'Tools: Conventional Commit' on: pull_request: - types: - - "opened" - - "edited" - - "synchronize" branches: - - "master" - - "v3" - - "v4.*" - - "v5.*" + - 'master' + - 'v3' + - 'v4.*' + - 'v5.*' + types: + - 'opened' + - 'edited' + - 'synchronize' + paths: + - '.github/workflows/conventional-commit.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: conventional-commit-check: runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: read + steps: - - name: conventional-commit-check - id: conventional-commit-check + - name: Check PR title format uses: agenthunt/conventional-commit-checker-action@9e552d650d0e205553ec7792d447929fc78e012b # v2.0.0 with: - pr-title-regex: '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([^)]+\))?!?: .+' - \ No newline at end of file + pr-title-regex: '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([^)]+\))?!?: .+' From 67b1983d856a828f59390486875f6e809f6bf8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:10:47 +0200 Subject: [PATCH 32/57] chore(github): fix action (#8973) --- .github/workflows/conventional-commit.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/conventional-commit.yml b/.github/workflows/conventional-commit.yml index 51626518a8..c5ba446edf 100644 --- a/.github/workflows/conventional-commit.yml +++ b/.github/workflows/conventional-commit.yml @@ -11,8 +11,6 @@ on: - 'opened' - 'edited' - 'synchronize' - paths: - - '.github/workflows/conventional-commit.yml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} From 18f3bc098cf5a02c4ef957651a05ac77ddf8f725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:27:33 +0200 Subject: [PATCH 33/57] chore(github): trigger only if repository is prowler (#8974) --- .github/workflows/api-pull-request.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index 58f7cdc38d..1266ce78c9 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -42,6 +42,7 @@ env: jobs: code-quality: + if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 30 permissions: @@ -77,6 +78,7 @@ jobs: run: poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/ security-scans: + if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -111,6 +113,7 @@ jobs: run: poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 . tests: + if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 30 permissions: @@ -174,6 +177,7 @@ jobs: flags: api dockerfile-lint: + if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -190,6 +194,7 @@ jobs: ignore: DL3013 container-build-and-scan: + if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 30 permissions: From fe768c0a3ea328658afe5f6d07d0964f6ae8b20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:39:39 +0200 Subject: [PATCH 34/57] chore(github): improve trufflehog action (#8977) --- .github/workflows/find-secrets.yml | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index d1258b3827..6428cf8f08 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -1,19 +1,33 @@ name: 'Tools: TruffleHog' -on: pull_request +on: + push: + branches: + - 'master' + - 'v5.*' + pull_request: + branches: + - 'master' + - 'v5.*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - trufflehog: + scan-secrets: runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@466da5b0bb161144f6afca9afe5d57975828c410 # v3.90.8 + + - name: Scan for secrets with TruffleHog + uses: trufflesecurity/trufflehog@ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # v3.90.11 with: - path: ./ - base: ${{ github.event.repository.default_branch }} - head: HEAD - extra_args: --only-verified + extra_args: '--results=verified,unknown' From f59690722392378f645bf354a7cae174fd03b437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:50:19 +0200 Subject: [PATCH 35/57] chore(github): improve labeler action (#8978) --- .github/workflows/labeler.yml | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 9dfa8993a1..fad94177f7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,17 +1,29 @@ -name: Prowler - PR Labeler +name: 'Tools: PR Labeler' on: - pull_request_target: - branches: - - "master" - - "v3" - - "v4.*" + pull_request_target: + branches: + - 'master' + - 'v5.*' + types: + - 'opened' + - 'reopened' + - 'synchronize' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: labeler: + runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read pull-requests: write - runs-on: ubuntu-latest + steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + - name: Apply labels to PR + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + with: + sync-labels: true From b4ff1dcc75b5ead68baf91055b095c68ae685560 Mon Sep 17 00:00:00 2001 From: Alan Buscaglia Date: Wed, 22 Oct 2025 15:51:43 +0200 Subject: [PATCH 36/57] refactor(graphs): graph components kebab case (#8966) Co-authored-by: alejandrobailo --- Makefile | 10 +- ui/components/graphs/BarChart.tsx | 162 -- ui/components/graphs/SankeyChart.tsx | 137 -- .../{DonutChart.tsx => donut-chart.tsx} | 29 +- ...lBarChart.tsx => horizontal-bar-chart.tsx} | 45 +- ui/components/graphs/index.ts | 18 +- .../graphs/{LineChart.tsx => line-chart.tsx} | 38 +- ui/components/graphs/map-chart.tsx | 479 +++++ ui/components/graphs/map-region-filter.tsx | 50 + .../{RadarChart.tsx => radar-chart.tsx} | 31 +- .../{RadialChart.tsx => radial-chart.tsx} | 7 +- ui/components/graphs/sankey-chart.tsx | 403 ++++ .../{ScatterPlot.tsx => scatter-plot.tsx} | 44 +- .../shared/{AlertPill.tsx => alert-pill.tsx} | 16 +- .../{ChartLegend.tsx => chart-legend.tsx} | 12 +- .../{ChartTooltip.tsx => chart-tooltip.tsx} | 77 +- ui/components/graphs/shared/constants.ts | 38 +- ui/components/graphs/types.ts | 8 + ui/components/ui/index.ts | 2 +- ui/components/ui/select/index.ts | 12 + ui/dependency-log.json | 254 ++- ui/package-lock.json | 1740 ++++++++++++++--- ui/package.json | 5 + ui/styles/globals.css | 70 + 24 files changed, 2899 insertions(+), 788 deletions(-) delete mode 100644 ui/components/graphs/BarChart.tsx delete mode 100644 ui/components/graphs/SankeyChart.tsx rename ui/components/graphs/{DonutChart.tsx => donut-chart.tsx} (84%) rename ui/components/graphs/{HorizontalBarChart.tsx => horizontal-bar-chart.tsx} (70%) rename ui/components/graphs/{LineChart.tsx => line-chart.tsx} (82%) create mode 100644 ui/components/graphs/map-chart.tsx create mode 100644 ui/components/graphs/map-region-filter.tsx rename ui/components/graphs/{RadarChart.tsx => radar-chart.tsx} (80%) rename ui/components/graphs/{RadialChart.tsx => radial-chart.tsx} (90%) create mode 100644 ui/components/graphs/sankey-chart.tsx rename ui/components/graphs/{ScatterPlot.tsx => scatter-plot.tsx} (81%) rename ui/components/graphs/shared/{AlertPill.tsx => alert-pill.tsx} (59%) rename ui/components/graphs/shared/{ChartLegend.tsx => chart-legend.tsx} (59%) rename ui/components/graphs/shared/{ChartTooltip.tsx => chart-tooltip.tsx} (57%) create mode 100644 ui/components/ui/select/index.ts diff --git a/Makefile b/Makefile index 368bb885bd..861c9cf7fe 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,14 @@ help: ## Show this help. @echo "Prowler Makefile" @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +##@ Build no cache +build-no-cache-dev: + docker compose -f docker-compose-dev.yml build --no-cache api-dev worker-dev worker-beat + ##@ Development Environment run-api-dev: ## Start development environment with API, PostgreSQL, Valkey, and workers - docker compose -f docker-compose-dev.yml up api-dev postgres valkey worker-dev worker-beat --build + docker compose -f docker-compose-dev.yml up api-dev postgres valkey worker-dev worker-beat + +##@ Development Environment +build-and-run-api-dev: build-no-cache-dev run-api-dev + diff --git a/ui/components/graphs/BarChart.tsx b/ui/components/graphs/BarChart.tsx deleted file mode 100644 index 8ebb7eca3b..0000000000 --- a/ui/components/graphs/BarChart.tsx +++ /dev/null @@ -1,162 +0,0 @@ -"use client"; - -import { - Bar, - BarChart as RechartsBar, - CartesianGrid, - Cell, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts"; - -import { ChartTooltip } from "./shared/ChartTooltip"; -import { CHART_COLORS, LAYOUT_OPTIONS } from "./shared/constants"; -import { getSeverityColorByName } from "./shared/utils"; -import { BarDataPoint, LayoutOption } from "./types"; - -interface BarChartProps { - data: BarDataPoint[]; - layout?: LayoutOption; - xLabel?: string; - yLabel?: string; - height?: number; - showValues?: boolean; -} - -const CustomLabel = ({ x, y, width, height, value, data }: any) => { - const percentage = data.percentage; - return ( - - {percentage !== undefined - ? `${percentage}% • ${value.toLocaleString()}` - : value.toLocaleString()} - - ); -}; - -export function BarChart({ - data, - layout = LAYOUT_OPTIONS.horizontal, - xLabel, - yLabel, - height = 400, - showValues = true, -}: BarChartProps) { - const isHorizontal = layout === LAYOUT_OPTIONS.horizontal; - - return ( - - - - {isHorizontal ? ( - <> - - - - ) : ( - <> - - - - )} - } /> - ( - - ) - : false - } - > - {data.map((entry, index) => ( - - ))} - - - - ); -} diff --git a/ui/components/graphs/SankeyChart.tsx b/ui/components/graphs/SankeyChart.tsx deleted file mode 100644 index d9c35cd1df..0000000000 --- a/ui/components/graphs/SankeyChart.tsx +++ /dev/null @@ -1,137 +0,0 @@ -"use client"; - -import { Rectangle, ResponsiveContainer, Sankey, Tooltip } from "recharts"; - -import { CHART_COLORS, SEVERITY_COLORS } from "./shared/constants"; - -interface SankeyNode { - name: string; -} - -interface SankeyLink { - source: number; - target: number; - value: number; -} - -interface SankeyChartProps { - data: { - nodes: SankeyNode[]; - links: SankeyLink[]; - }; - height?: number; -} - -const COLORS: Record = { - Success: "var(--color-success)", - Fail: "var(--color-destructive)", - AWS: "var(--color-orange)", - Azure: "var(--color-cyan)", - Google: "var(--color-red)", - ...SEVERITY_COLORS, -}; - -const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length) { - const data = payload[0].payload; - return ( -
-

{data.name}

- {data.value && ( -

Value: {data.value}

- )} -
- ); - } - return null; -}; - -const CustomNode = ({ x, y, width, height, payload, containerWidth }: any) => { - const isOut = x + width + 6 > containerWidth; - const nodeName = payload.name; - const color = COLORS[nodeName] || CHART_COLORS.defaultColor; - - return ( - - - - {nodeName} - - - {payload.value} - - - ); -}; - -const CustomLink = (props: any) => { - const { - sourceX, - targetX, - sourceY, - targetY, - sourceControlX, - targetControlX, - linkWidth, - } = props; - - const sourceName = props.payload.source?.name || ""; - const color = COLORS[sourceName] || CHART_COLORS.defaultColor; - - return ( - - - - ); -}; - -export function SankeyChart({ data, height = 400 }: SankeyChartProps) { - return ( - - } - link={} - nodePadding={50} - margin={{ top: 20, right: 160, bottom: 20, left: 160 }} - > - } /> - - - ); -} diff --git a/ui/components/graphs/DonutChart.tsx b/ui/components/graphs/donut-chart.tsx similarity index 84% rename from ui/components/graphs/DonutChart.tsx rename to ui/components/graphs/donut-chart.tsx index 57713cc2f6..ea5ff49e5f 100644 --- a/ui/components/graphs/DonutChart.tsx +++ b/ui/components/graphs/donut-chart.tsx @@ -5,7 +5,7 @@ import { Cell, Label, Pie, PieChart, Tooltip } from "recharts"; import { ChartConfig, ChartContainer } from "@/components/ui/chart/Chart"; -import { ChartLegend } from "./shared/ChartLegend"; +import { ChartLegend } from "./shared/chart-legend"; import { DonutDataPoint } from "./types"; interface DonutChartProps { @@ -24,18 +24,30 @@ const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; return ( -
+
- + {data.percentage}% {data.name}
{data.change !== undefined && ( -

+

{data.change > 0 ? "+" : ""} {data.change}% @@ -145,14 +157,19 @@ export function DonutChart({ {formattedValue} {centerLabel.label} diff --git a/ui/components/graphs/HorizontalBarChart.tsx b/ui/components/graphs/horizontal-bar-chart.tsx similarity index 70% rename from ui/components/graphs/HorizontalBarChart.tsx rename to ui/components/graphs/horizontal-bar-chart.tsx index b91e575656..c792d60d38 100644 --- a/ui/components/graphs/HorizontalBarChart.tsx +++ b/ui/components/graphs/horizontal-bar-chart.tsx @@ -26,7 +26,12 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {

{title && (
-

{title}

+

+ {title} +

)} @@ -48,8 +53,9 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) { >
{isHovered && ( -
+
- + {item.value.toLocaleString()} {item.name} Risk
{item.newFindings !== undefined && (
- - + + {item.newFindings} New Findings
)} {item.change !== undefined && ( -

+

{item.change > 0 ? "+" : ""} {item.change}% @@ -102,15 +126,16 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {

{item.percentage}% - - {item.value.toLocaleString()} + + {item.value.toLocaleString()}
); diff --git a/ui/components/graphs/index.ts b/ui/components/graphs/index.ts index f0f0e53697..79d9b01e53 100644 --- a/ui/components/graphs/index.ts +++ b/ui/components/graphs/index.ts @@ -1,9 +1,9 @@ -export { BarChart } from "./BarChart"; -export { DonutChart } from "./DonutChart"; -export { HorizontalBarChart } from "./HorizontalBarChart"; -export { LineChart } from "./LineChart"; -export { RadarChart } from "./RadarChart"; -export { RadialChart } from "./RadialChart"; -export { SankeyChart } from "./SankeyChart"; -export { ScatterPlot } from "./ScatterPlot"; -export { ChartLegend, type ChartLegendItem } from "./shared/ChartLegend"; +export { DonutChart } from "./donut-chart"; +export { HorizontalBarChart } from "./horizontal-bar-chart"; +export { LineChart } from "./line-chart"; +export { MapChart, type MapChartData, type MapChartProps } from "./map-chart"; +export { RadarChart } from "./radar-chart"; +export { RadialChart } from "./radial-chart"; +export { SankeyChart } from "./sankey-chart"; +export { ScatterPlot } from "./scatter-plot"; +export { ChartLegend, type ChartLegendItem } from "./shared/chart-legend"; diff --git a/ui/components/graphs/LineChart.tsx b/ui/components/graphs/line-chart.tsx similarity index 82% rename from ui/components/graphs/LineChart.tsx rename to ui/components/graphs/line-chart.tsx index b19c9591f5..daefb55808 100644 --- a/ui/components/graphs/LineChart.tsx +++ b/ui/components/graphs/line-chart.tsx @@ -14,8 +14,8 @@ import { YAxis, } from "recharts"; -import { AlertPill } from "./shared/AlertPill"; -import { ChartLegend } from "./shared/ChartLegend"; +import { AlertPill } from "./shared/alert-pill"; +import { ChartLegend } from "./shared/chart-legend"; import { CHART_COLORS } from "./shared/constants"; import { LineConfig, LineDataPoint } from "./types"; @@ -48,8 +48,19 @@ const CustomLineTooltip = ({ const totalValue = typedPayload.reduce((sum, item) => sum + item.value, 0); return ( -
-

{label}

+
+

+ {label} +

@@ -67,18 +78,29 @@ const CustomLineTooltip = ({ className="h-2 w-2 rounded-full" style={{ backgroundColor: item.stroke }} /> - {item.value} + + {item.value} +
{newFindings !== undefined && (
- - + + {newFindings} New Findings
)} {change !== undefined && typeof change === "number" && ( -

+

{change > 0 ? "+" : ""} {change}% diff --git a/ui/components/graphs/map-chart.tsx b/ui/components/graphs/map-chart.tsx new file mode 100644 index 0000000000..586bb47b25 --- /dev/null +++ b/ui/components/graphs/map-chart.tsx @@ -0,0 +1,479 @@ +"use client"; + +import * as d3 from "d3"; +import type { + Feature, + FeatureCollection, + GeoJsonProperties, + Geometry, +} from "geojson"; +import { AlertTriangle, Info, MapPin } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { feature } from "topojson-client"; +import type { + GeometryCollection, + Objects, + Topology, +} from "topojson-specification"; + +import { HorizontalBarChart } from "./horizontal-bar-chart"; +import { BarDataPoint } from "./types"; + +// Constants +const MAP_CONFIG = { + defaultWidth: 688, + defaultHeight: 400, + pointRadius: 6, + selectedPointRadius: 8, + transitionDuration: 300, +} as const; + +const MAP_COLORS = { + landFill: "var(--chart-border-emphasis)", + landStroke: "var(--chart-border)", + pointDefault: "#DB2B49", + pointSelected: "#86DA26", + pointHover: "#DB2B49", +} as const; + +const RISK_LEVELS = { + LOW_HIGH: "low-high", + HIGH: "high", + CRITICAL: "critical", +} as const; + +type RiskLevel = (typeof RISK_LEVELS)[keyof typeof RISK_LEVELS]; + +interface LocationPoint { + id: string; + name: string; + region: string; + coordinates: [number, number]; + totalFindings: number; + riskLevel: RiskLevel; + severityData: BarDataPoint[]; + change?: number; +} + +export interface MapChartData { + locations: LocationPoint[]; + regions: string[]; +} + +export interface MapChartProps { + data: MapChartData; + height?: number; + onLocationSelect?: (location: LocationPoint | null) => void; +} + +// Utility functions +function createProjection(width: number, height: number) { + return d3 + .geoNaturalEarth1() + .fitExtent( + [ + [1, 1], + [width - 1, height - 1], + ], + { type: "Sphere" }, + ) + .precision(0.2); +} + +async function fetchWorldData(): Promise { + try { + const worldAtlasModule = await import("world-atlas/countries-110m.json"); + const worldData = worldAtlasModule.default || worldAtlasModule; + const topology = worldData as unknown as Topology; + return feature( + topology, + topology.objects.countries as GeometryCollection, + ) as FeatureCollection; + } catch (error) { + console.error("Error loading world map data:", error); + return null; + } +} + +// Helper: Create SVG element +function createSVGElement( + type: string, + attributes: Record, +): T { + const element = document.createElementNS( + "http://www.w3.org/2000/svg", + type, + ) as T; + Object.entries(attributes).forEach(([key, value]) => { + element.setAttribute(key, value); + }); + return element; +} + +// Components +function MapTooltip({ + location, + position, +}: { + location: LocationPoint; + position: { x: number; y: number }; +}) { + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textPrimary: "var(--chart-text-primary)", + textSecondary: "var(--chart-text-secondary)", + }; + + return ( +

+
+ + + {location.name} + +
+
+ + + {location.totalFindings.toLocaleString()} Fail Findings + +
+ {location.change !== undefined && ( +

+ + {location.change > 0 ? "+" : ""} + {location.change}% + {" "} + since last scan +

+ )} +
+ ); +} + +function EmptyState() { + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textSecondary: "var(--chart-text-secondary)", + }; + + return ( +
+
+ +

+ Select a location on the map to view details +

+
+
+ ); +} + +function LoadingState({ height }: { height: number }) { + const CHART_COLORS = { + textSecondary: "var(--chart-text-secondary)", + }; + + return ( +
+
+
+ Loading map... +
+
+
+ ); +} + +export function MapChart({ + data, + height = MAP_CONFIG.defaultHeight, +}: MapChartProps) { + const svgRef = useRef(null); + const containerRef = useRef(null); + const [selectedLocation, setSelectedLocation] = + useState(null); + const [hoveredLocation, setHoveredLocation] = useState( + null, + ); + const [tooltipPosition, setTooltipPosition] = useState<{ + x: number; + y: number; + } | null>(null); + const [worldData, setWorldData] = useState(null); + const [isLoadingMap, setIsLoadingMap] = useState(true); + const [dimensions, setDimensions] = useState<{ + width: number; + height: number; + }>({ + width: MAP_CONFIG.defaultWidth, + height, + }); + + // Fetch world data once on mount + useEffect(() => { + let isMounted = true; + fetchWorldData() + .then((data) => { + if (isMounted && data) setWorldData(data); + }) + .catch(console.error) + .finally(() => { + if (isMounted) setIsLoadingMap(false); + }); + return () => { + isMounted = false; + }; + }, []); + + // Update dimensions on resize + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + setDimensions({ width: containerRef.current.clientWidth, height }); + } + }; + updateDimensions(); + window.addEventListener("resize", updateDimensions); + return () => window.removeEventListener("resize", updateDimensions); + }, [height]); + + // Render the map + useEffect(() => { + if (!svgRef.current || !worldData || isLoadingMap) return; + + const svg = svgRef.current; + const { width, height } = dimensions; + svg.innerHTML = ""; + + const projection = createProjection(width, height); + const path = d3.geoPath().projection(projection); + + // Render countries + const mapGroup = createSVGElement("g", { + class: "map-countries", + }); + worldData.features?.forEach( + (feature: Feature) => { + const pathData = path(feature); + if (pathData) { + const pathElement = createSVGElement("path", { + d: pathData, + fill: MAP_COLORS.landFill, + stroke: MAP_COLORS.landStroke, + "stroke-width": "0.5", + }); + mapGroup.appendChild(pathElement); + } + }, + ); + svg.appendChild(mapGroup); + + // Helper to update tooltip position + const updateTooltip = (e: MouseEvent) => { + const rect = svg.getBoundingClientRect(); + setTooltipPosition({ + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }); + }; + + // Helper to create circle + const createCircle = (location: LocationPoint) => { + const projected = projection(location.coordinates); + if (!projected) return null; + + const [x, y] = projected; + if (x < 0 || x > width || y < 0 || y > height) return null; + + const isSelected = selectedLocation?.id === location.id; + const isHovered = hoveredLocation?.id === location.id; + const classes = ["cursor-pointer"]; + + if (isSelected) classes.push("drop-shadow-[0_0_8px_#86da26]"); + if (isHovered && !isSelected) classes.push("opacity-70"); + + const circle = createSVGElement("circle", { + cx: x.toString(), + cy: y.toString(), + r: (isSelected + ? MAP_CONFIG.selectedPointRadius + : MAP_CONFIG.pointRadius + ).toString(), + fill: isSelected ? MAP_COLORS.pointSelected : MAP_COLORS.pointDefault, + class: classes.join(" "), + }); + + circle.addEventListener("click", () => + setSelectedLocation(isSelected ? null : location), + ); + circle.addEventListener("mouseenter", (e) => { + setHoveredLocation(location); + updateTooltip(e); + }); + circle.addEventListener("mousemove", updateTooltip); + circle.addEventListener("mouseleave", () => { + setHoveredLocation(null); + setTooltipPosition(null); + }); + + return circle; + }; + + // Render points + const pointsGroup = createSVGElement("g", { + class: "threat-points", + }); + + // Unselected points first + data.locations.forEach((location) => { + if (selectedLocation?.id !== location.id) { + const circle = createCircle(location); + if (circle) pointsGroup.appendChild(circle); + } + }); + + // Selected point last (on top) + if (selectedLocation) { + const selectedData = data.locations.find( + (loc) => loc.id === selectedLocation.id, + ); + if (selectedData) { + const circle = createCircle(selectedData); + if (circle) pointsGroup.appendChild(circle); + } + } + + svg.appendChild(pointsGroup); + }, [ + data.locations, + dimensions, + selectedLocation, + hoveredLocation, + worldData, + isLoadingMap, + ]); + + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textPrimary: "var(--chart-text-primary)", + textSecondary: "var(--chart-text-secondary)", + }; + + return ( +
+ {/* Map Section */} +
+

+ Threat Map +

+ +
+ {isLoadingMap ? ( + + ) : ( + <> +
+ + {hoveredLocation && tooltipPosition && ( + + )} +
+
+
+ + {data.locations.length} Locations + +
+ + )} +
+
+ + {/* Details Section */} +
+
+ {selectedLocation ? ( +
+
+
+
+

+ {selectedLocation.name} +

+
+

+ {selectedLocation.totalFindings.toLocaleString()} Total Findings +

+
+ +
+ ) : ( + + )} +
+
+ ); +} diff --git a/ui/components/graphs/map-region-filter.tsx b/ui/components/graphs/map-region-filter.tsx new file mode 100644 index 0000000000..3b483d1c62 --- /dev/null +++ b/ui/components/graphs/map-region-filter.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select/Select"; + +interface MapRegionFilterProps { + regions: string[]; + selectedRegion: string; + onRegionChange: (region: string) => void; + chartColors: { + tooltipBorder: string; + tooltipBackground: string; + textPrimary: string; + }; +} + +export function MapRegionFilter({ + regions, + selectedRegion, + onRegionChange, + chartColors, +}: MapRegionFilterProps) { + return ( + + ); +} diff --git a/ui/components/graphs/RadarChart.tsx b/ui/components/graphs/radar-chart.tsx similarity index 80% rename from ui/components/graphs/RadarChart.tsx rename to ui/components/graphs/radar-chart.tsx index 462c29dd68..ab3a43e6da 100644 --- a/ui/components/graphs/RadarChart.tsx +++ b/ui/components/graphs/radar-chart.tsx @@ -13,7 +13,7 @@ import { ChartTooltip, } from "@/components/ui/chart/Chart"; -import { AlertPill } from "./shared/AlertPill"; +import { AlertPill } from "./shared/alert-pill"; import { CHART_COLORS } from "./shared/constants"; import { RadarDataPoint } from "./types"; @@ -28,7 +28,7 @@ interface RadarChartProps { const chartConfig = { value: { label: "Findings", - color: "var(--color-magenta)", + color: "var(--chart-radar-primary)", }, } satisfies ChartConfig; @@ -36,15 +36,27 @@ const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0]; return ( -
-

+

+

{data.payload.category}

{data.payload.change !== undefined && ( -

+

{data.payload.change > 0 ? "+" : ""} {data.payload.change}% @@ -84,8 +96,11 @@ const CustomDot = (props: any) => { cx={cx} cy={cy} r={isSelected ? 9 : 6} - fill={isSelected ? "var(--color-success)" : "var(--color-purple-dark)"} + fill={ + isSelected ? "var(--chart-success-color)" : "var(--chart-radar-primary)" + } fillOpacity={1} + className={isSelected ? "drop-shadow-[0_0_8px_#86da26]" : ""} style={{ cursor: onSelectPoint ? "pointer" : "default", pointerEvents: "all", @@ -117,7 +132,7 @@ export function RadarChart({ {percentage}% diff --git a/ui/components/graphs/sankey-chart.tsx b/ui/components/graphs/sankey-chart.tsx new file mode 100644 index 0000000000..58179aadf6 --- /dev/null +++ b/ui/components/graphs/sankey-chart.tsx @@ -0,0 +1,403 @@ +"use client"; + +import { useState } from "react"; +import { Rectangle, ResponsiveContainer, Sankey, Tooltip } from "recharts"; + +import { ChartTooltip } from "./shared/chart-tooltip"; +import { CHART_COLORS } from "./shared/constants"; + +interface SankeyNode { + name: string; + newFindings?: number; + change?: number; +} + +interface SankeyLink { + source: number; + target: number; + value: number; +} + +interface SankeyChartProps { + data: { + nodes: SankeyNode[]; + links: SankeyLink[]; + }; + height?: number; +} + +interface LinkTooltipState { + show: boolean; + x: number; + y: number; + sourceName: string; + targetName: string; + value: number; + color: string; +} + +interface NodeTooltipState { + show: boolean; + x: number; + y: number; + name: string; + value: number; + color: string; + newFindings?: number; + change?: number; +} + +// Note: Using hex colors directly because Recharts SVG fill doesn't resolve CSS variables +const COLORS: Record = { + Success: "#86da26", + Fail: "#db2b49", + AWS: "#ff9900", + Azure: "#00bcd4", + Google: "#EA4335", + Critical: "#971348", + High: "#ff3077", + Medium: "#ff7d19", + Low: "#fdd34f", + Info: "#2e51b2", + Informational: "#2e51b2", +}; + +const CustomTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + const data = payload[0].payload; + return ( +

+

+ {data.name} +

+ {data.value && ( +

+ Value: {data.value} +

+ )} +
+ ); + } + return null; +}; + +const CustomNode = (props: any) => { + const { x, y, width, height, payload, containerWidth } = props; + const isOut = x + width + 6 > containerWidth; + const nodeName = payload.name; + const color = COLORS[nodeName] || CHART_COLORS.defaultColor; + const isHidden = nodeName === ""; + const hasTooltip = !isHidden && payload.newFindings; + + const handleMouseEnter = (e: React.MouseEvent) => { + if (!hasTooltip) return; + + const rect = e.currentTarget.closest("svg") as SVGSVGElement; + if (rect) { + const bbox = rect.getBoundingClientRect(); + props.onNodeHover?.({ + x: e.clientX - bbox.left, + y: e.clientY - bbox.top, + name: nodeName, + value: payload.value, + color, + newFindings: payload.newFindings, + change: payload.change, + }); + } + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (!hasTooltip) return; + + const rect = e.currentTarget.closest("svg") as SVGSVGElement; + if (rect) { + const bbox = rect.getBoundingClientRect(); + props.onNodeMove?.({ + x: e.clientX - bbox.left, + y: e.clientY - bbox.top, + }); + } + }; + + const handleMouseLeave = () => { + if (!hasTooltip) return; + props.onNodeLeave?.(); + }; + + return ( + + + {!isHidden && ( + <> + + {nodeName} + + + {payload.value} + + + )} + + ); +}; + +const CustomLink = (props: any) => { + const { + sourceX, + targetX, + sourceY, + targetY, + sourceControlX, + targetControlX, + linkWidth, + index, + } = props; + + const sourceName = props.payload.source?.name || ""; + const targetName = props.payload.target?.name || ""; + const value = props.payload.value || 0; + const color = COLORS[sourceName] || CHART_COLORS.defaultColor; + const isHidden = targetName === ""; + + const isHovered = props.hoveredLink !== null && props.hoveredLink === index; + const hasHoveredLink = props.hoveredLink !== null; + + const pathD = ` + M${sourceX},${sourceY + linkWidth / 2} + C${sourceControlX},${sourceY + linkWidth / 2} + ${targetControlX},${targetY + linkWidth / 2} + ${targetX},${targetY + linkWidth / 2} + L${targetX},${targetY - linkWidth / 2} + C${targetControlX},${targetY - linkWidth / 2} + ${sourceControlX},${sourceY - linkWidth / 2} + ${sourceX},${sourceY - linkWidth / 2} + Z + `; + + const getOpacity = () => { + if (isHidden) return "0"; + if (!hasHoveredLink) return "0.4"; + return isHovered ? "0.8" : "0.1"; + }; + + const handleMouseEnter = (e: React.MouseEvent) => { + const rect = e.currentTarget.parentElement?.parentElement + ?.parentElement as unknown as SVGSVGElement; + if (rect) { + const bbox = rect.getBoundingClientRect(); + props.onLinkHover?.(index, { + x: e.clientX - bbox.left, + y: e.clientY - bbox.top, + sourceName, + targetName, + value, + color, + }); + } + }; + + const handleMouseMove = (e: React.MouseEvent) => { + const rect = e.currentTarget.parentElement?.parentElement + ?.parentElement as unknown as SVGSVGElement; + if (rect && isHovered) { + const bbox = rect.getBoundingClientRect(); + props.onLinkMove?.({ + x: e.clientX - bbox.left, + y: e.clientY - bbox.top, + }); + } + }; + + const handleMouseLeave = () => { + props.onLinkLeave?.(); + }; + + return ( + + + + ); +}; + +export function SankeyChart({ data, height = 400 }: SankeyChartProps) { + const [hoveredLink, setHoveredLink] = useState(null); + const [linkTooltip, setLinkTooltip] = useState({ + show: false, + x: 0, + y: 0, + sourceName: "", + targetName: "", + value: 0, + color: "", + }); + + const [nodeTooltip, setNodeTooltip] = useState({ + show: false, + x: 0, + y: 0, + name: "", + value: 0, + color: "", + }); + + const handleLinkHover = ( + index: number, + data: Omit, + ) => { + setHoveredLink(index); + setLinkTooltip({ show: true, ...data }); + }; + + const handleLinkMove = (position: { x: number; y: number }) => { + setLinkTooltip((prev) => ({ + ...prev, + x: position.x, + y: position.y, + })); + }; + + const handleLinkLeave = () => { + setHoveredLink(null); + setLinkTooltip((prev) => ({ ...prev, show: false })); + }; + + const handleNodeHover = (data: Omit) => { + setNodeTooltip({ show: true, ...data }); + }; + + const handleNodeMove = (position: { x: number; y: number }) => { + setNodeTooltip((prev) => ({ + ...prev, + x: position.x, + y: position.y, + })); + }; + + const handleNodeLeave = () => { + setNodeTooltip((prev) => ({ ...prev, show: false })); + }; + + return ( +
+ + + } + link={ + + } + nodePadding={50} + margin={{ top: 20, right: 160, bottom: 20, left: 160 }} + sort={false} + > + } /> + + + {linkTooltip.show && ( +
+ +
+ )} + {nodeTooltip.show && ( +
+ +
+ )} +
+ ); +} diff --git a/ui/components/graphs/ScatterPlot.tsx b/ui/components/graphs/scatter-plot.tsx similarity index 81% rename from ui/components/graphs/ScatterPlot.tsx rename to ui/components/graphs/scatter-plot.tsx index 920f6c0c10..a0725f0dc5 100644 --- a/ui/components/graphs/ScatterPlot.tsx +++ b/ui/components/graphs/scatter-plot.tsx @@ -11,18 +11,11 @@ import { YAxis, } from "recharts"; -import { AlertPill } from "./shared/AlertPill"; -import { ChartLegend } from "./shared/ChartLegend"; +import { AlertPill } from "./shared/alert-pill"; +import { ChartLegend } from "./shared/chart-legend"; import { CHART_COLORS } from "./shared/constants"; import { getSeverityColorByRiskScore } from "./shared/utils"; - -interface ScatterDataPoint { - x: number; - y: number; - provider: string; - name: string; - size?: number; -} +import type { ScatterDataPoint } from "./types"; interface ScatterPlotProps { data: ScatterDataPoint[]; @@ -34,9 +27,9 @@ interface ScatterPlotProps { } const PROVIDER_COLORS = { - AWS: "var(--color-orange)", - Azure: "var(--color-cyan)", - Google: "var(--color-red)", + AWS: "var(--chart-provider-aws)", + Azure: "var(--chart-provider-azure)", + Google: "var(--chart-provider-google)", }; const CustomTooltip = ({ active, payload }: any) => { @@ -45,9 +38,23 @@ const CustomTooltip = ({ active, payload }: any) => { const severityColor = getSeverityColorByRiskScore(data.x); return ( -
-

{data.name}

-

+

+

+ {data.name} +

+

{data.x} Risk Score

@@ -69,7 +76,7 @@ const CustomScatterDot = ({ const isSelected = selectedPoint?.name === payload.name; const size = isSelected ? 18 : 8; const fill = isSelected - ? "var(--color-success)" + ? "#86DA26" : PROVIDER_COLORS[payload.provider as keyof typeof PROVIDER_COLORS] || CHART_COLORS.defaultColor; @@ -79,8 +86,9 @@ const CustomScatterDot = ({ cy={cy} r={size / 2} fill={fill} - stroke={isSelected ? "var(--color-success)" : "transparent"} + stroke={isSelected ? "#86DA26" : "transparent"} strokeWidth={2} + className={isSelected ? "drop-shadow-[0_0_8px_#86da26]" : ""} style={{ cursor: "pointer" }} onClick={() => onSelectPoint?.(payload)} /> diff --git a/ui/components/graphs/shared/AlertPill.tsx b/ui/components/graphs/shared/alert-pill.tsx similarity index 59% rename from ui/components/graphs/shared/AlertPill.tsx rename to ui/components/graphs/shared/alert-pill.tsx index f611b27766..2da8b1903f 100644 --- a/ui/components/graphs/shared/AlertPill.tsx +++ b/ui/components/graphs/shared/alert-pill.tsx @@ -17,13 +17,17 @@ export function AlertPill({ }: AlertPillProps) { return (
-
- +
+ {value} diff --git a/ui/components/graphs/shared/ChartLegend.tsx b/ui/components/graphs/shared/chart-legend.tsx similarity index 59% rename from ui/components/graphs/shared/ChartLegend.tsx rename to ui/components/graphs/shared/chart-legend.tsx index 11066aa54a..2efea3ea85 100644 --- a/ui/components/graphs/shared/ChartLegend.tsx +++ b/ui/components/graphs/shared/chart-legend.tsx @@ -9,14 +9,22 @@ interface ChartLegendProps { export function ChartLegend({ items }: ChartLegendProps) { return ( -
+
{items.map((item, index) => (
- {item.label} + + {item.label} +
))}
diff --git a/ui/components/graphs/shared/ChartTooltip.tsx b/ui/components/graphs/shared/chart-tooltip.tsx similarity index 57% rename from ui/components/graphs/shared/ChartTooltip.tsx rename to ui/components/graphs/shared/chart-tooltip.tsx index 6b4bde27bc..432a15713d 100644 --- a/ui/components/graphs/shared/ChartTooltip.tsx +++ b/ui/components/graphs/shared/chart-tooltip.tsx @@ -3,6 +3,7 @@ import { Bell, VolumeX } from "lucide-react"; import { cn } from "@/lib/utils"; import { TooltipData } from "../types"; +import { CHART_COLORS } from "./constants"; interface ChartTooltipProps { active?: boolean; @@ -27,7 +28,13 @@ export function ChartTooltip({ const color = payload[0].color || data.color; return ( -
+
{showColorIndicator && color && (
)} -

{label || data.name}

+

+ {label || data.name} +

-

+

{typeof data.value === "number" ? data.value.toLocaleString() : data.value} @@ -50,8 +62,11 @@ export function ChartTooltip({ {data.newFindings !== undefined && data.newFindings > 0 && (

- - + + {data.newFindings} New Findings
@@ -59,20 +74,33 @@ export function ChartTooltip({ {data.new !== undefined && data.new > 0 && (
- - {data.new} New + + + {data.new} New +
)} {data.muted !== undefined && data.muted > 0 && (
- - {data.muted} Muted + + + {data.muted} Muted +
)} {data.change !== undefined && ( -

+

{data.change > 0 ? "+" : ""} {data.change}% @@ -97,8 +125,19 @@ export function MultiSeriesChartTooltip({ } return ( -

-

{label}

+
+

+ {label} +

{payload.map((entry: any, index: number) => (
@@ -106,12 +145,20 @@ export function MultiSeriesChartTooltip({ className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }} /> - {entry.name}: - + + {entry.name}: + + {entry.value} {entry.payload[`${entry.dataKey}_change`] && ( - + ({entry.payload[`${entry.dataKey}_change`] > 0 ? "+" : ""} {entry.payload[`${entry.dataKey}_change`]}%) diff --git a/ui/components/graphs/shared/constants.ts b/ui/components/graphs/shared/constants.ts index 9ab80b8fa8..4aadd4aac0 100644 --- a/ui/components/graphs/shared/constants.ts +++ b/ui/components/graphs/shared/constants.ts @@ -1,21 +1,33 @@ export const SEVERITY_COLORS = { - Informational: "var(--color-info)", - Low: "var(--color-warning)", - Medium: "var(--color-warning-emphasis)", - High: "var(--color-danger)", - Critical: "var(--color-danger-emphasis)", + Informational: "var(--chart-info)", + Info: "var(--chart-info)", + Low: "var(--chart-warning)", + Medium: "var(--chart-warning-emphasis)", + High: "var(--chart-danger)", + Critical: "var(--chart-danger-emphasis)", +} as const; + +export const PROVIDER_COLORS = { + AWS: "var(--chart-provider-aws)", + Azure: "var(--chart-provider-azure)", + Google: "var(--chart-provider-google)", +} as const; + +export const STATUS_COLORS = { + Success: "var(--chart-success-color)", + Fail: "var(--chart-fail)", } as const; export const CHART_COLORS = { - tooltipBorder: "var(--color-slate-700)", - tooltipBackground: "var(--color-slate-800)", - textPrimary: "var(--color-white)", - textSecondary: "var(--color-slate-400)", - gridLine: "var(--color-slate-700)", + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textPrimary: "var(--chart-text-primary)", + textSecondary: "var(--chart-text-secondary)", + gridLine: "var(--chart-border-emphasis)", backgroundTrack: "rgba(51, 65, 85, 0.5)", // slate-700 with 50% opacity - alertPillBg: "var(--color-alert-pill-bg)", - alertPillText: "var(--color-alert-pill-text)", - defaultColor: "var(--color-slate-500)", // Default fallback color for charts + alertPillBg: "var(--chart-alert-bg)", + alertPillText: "var(--chart-alert-text)", + defaultColor: "#64748b", // slate-500 } as const; export const CHART_DIMENSIONS = { diff --git a/ui/components/graphs/types.ts b/ui/components/graphs/types.ts index 0961a32105..f7a9b02542 100644 --- a/ui/components/graphs/types.ts +++ b/ui/components/graphs/types.ts @@ -36,6 +36,14 @@ export interface RadarDataPoint { change?: number; } +export interface ScatterDataPoint { + x: number; + y: number; + provider: string; + name: string; + size?: number; +} + export interface LineConfig { dataKey: string; color: string; diff --git a/ui/components/ui/index.ts b/ui/components/ui/index.ts index 205ec7fd50..c4059a7b55 100644 --- a/ui/components/ui/index.ts +++ b/ui/components/ui/index.ts @@ -12,6 +12,6 @@ export * from "./feedback-banner/feedback-banner"; export * from "./headers/navigation-header"; export * from "./label/Label"; export * from "./main-layout/main-layout"; -export * from "./select/Select"; +export * from "./select"; export * from "./sidebar"; export * from "./toast"; diff --git a/ui/components/ui/select/index.ts b/ui/components/ui/select/index.ts new file mode 100644 index 0000000000..730dd10551 --- /dev/null +++ b/ui/components/ui/select/index.ts @@ -0,0 +1,12 @@ +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} from "./Select"; diff --git a/ui/dependency-log.json b/ui/dependency-log.json index 64a12b5901..4677a082eb 100644 --- a/ui/dependency-log.json +++ b/ui/dependency-log.json @@ -5,7 +5,7 @@ "from": "1.0.59", "to": "1.0.59", "strategy": "installed", - "generatedAt": "2025-10-01T11:13:12.025Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -13,7 +13,7 @@ "from": "2.0.59", "to": "2.0.59", "strategy": "installed", - "generatedAt": "2025-10-01T11:13:12.025Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -21,15 +21,15 @@ "from": "2.8.4", "to": "2.8.4", "strategy": "installed", - "generatedAt": "2025-09-29T14:26:25.838Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "@hookform/resolvers", - "from": "3.10.0", + "from": "5.2.2", "to": "5.2.2", "strategy": "installed", - "generatedAt": "2025-10-01T15:09:44.056Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -37,7 +37,7 @@ "from": "0.3.77", "to": "0.3.77", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -45,23 +45,23 @@ "from": "0.4.9", "to": "0.4.9", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "@langchain/langgraph-supervisor", - "from": "0.0.12", + "from": "0.0.20", "to": "0.0.20", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "@langchain/openai", - "from": "0.6.9", + "from": "0.5.18", "to": "0.5.18", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -69,7 +69,7 @@ "from": "15.3.5", "to": "15.3.5", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -77,7 +77,7 @@ "from": "1.1.14", "to": "1.1.14", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -85,7 +85,7 @@ "from": "1.1.14", "to": "1.1.14", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -93,7 +93,7 @@ "from": "2.1.15", "to": "2.1.15", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -101,7 +101,7 @@ "from": "1.3.2", "to": "1.3.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -109,7 +109,7 @@ "from": "2.1.7", "to": "2.1.7", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -117,7 +117,7 @@ "from": "2.2.5", "to": "2.2.5", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -125,7 +125,7 @@ "from": "1.2.3", "to": "1.2.3", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -133,7 +133,7 @@ "from": "1.2.14", "to": "1.2.14", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -141,7 +141,7 @@ "from": "3.9.4", "to": "3.9.4", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -149,7 +149,7 @@ "from": "3.8.12", "to": "3.8.12", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -157,7 +157,7 @@ "from": "4.1.13", "to": "4.1.13", "strategy": "installed", - "generatedAt": "2025-09-24T15:04:48.761Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -165,7 +165,7 @@ "from": "0.5.16", "to": "0.5.16", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -173,7 +173,7 @@ "from": "8.21.3", "to": "8.21.3", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -181,15 +181,15 @@ "from": "4.0.9", "to": "4.0.9", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "ai", - "from": "4.3.16", + "from": "5.0.59", "to": "5.0.59", "strategy": "installed", - "generatedAt": "2025-10-01T10:03:22.788Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -197,7 +197,7 @@ "from": "6.0.2", "to": "6.0.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -205,7 +205,7 @@ "from": "0.7.1", "to": "0.7.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -213,7 +213,15 @@ "from": "2.1.1", "to": "2.1.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "dependencies", + "name": "d3", + "from": "7.9.0", + "to": "7.9.0", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -221,7 +229,7 @@ "from": "4.1.0", "to": "4.1.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -229,7 +237,7 @@ "from": "11.18.2", "to": "11.18.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -237,7 +245,7 @@ "from": "10.7.16", "to": "10.7.16", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -245,7 +253,7 @@ "from": "5.10.0", "to": "5.10.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -253,7 +261,7 @@ "from": "4.1.0", "to": "4.1.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -261,7 +269,7 @@ "from": "4.0.0", "to": "4.0.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -269,7 +277,7 @@ "from": "0.543.0", "to": "0.543.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -277,15 +285,15 @@ "from": "15.0.12", "to": "15.0.12", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "next", - "from": "14.2.32", + "from": "15.5.3", "to": "15.5.3", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -293,7 +301,7 @@ "from": "5.0.0-beta.29", "to": "5.0.0-beta.29", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -301,7 +309,7 @@ "from": "0.2.1", "to": "0.2.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -309,23 +317,23 @@ "from": "1.4.2", "to": "1.4.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "react", - "from": "18.3.1", + "from": "19.1.1", "to": "19.1.1", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "react-dom", - "from": "18.3.1", + "from": "19.1.1", "to": "19.1.1", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -333,7 +341,7 @@ "from": "7.62.0", "to": "7.62.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -341,7 +349,7 @@ "from": "10.1.0", "to": "10.1.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -349,7 +357,7 @@ "from": "2.15.4", "to": "2.15.4", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -357,7 +365,7 @@ "from": "3.13.0", "to": "3.13.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -365,15 +373,7 @@ "from": "0.0.1", "to": "0.0.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" - }, - { - "section": "dependencies", - "name": "shadcn", - "from": "3.2.1", - "to": "3.2.1", - "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -381,7 +381,7 @@ "from": "0.33.5", "to": "0.33.5", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -389,7 +389,7 @@ "from": "3.3.1", "to": "3.3.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -397,7 +397,15 @@ "from": "1.0.7", "to": "1.0.7", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "dependencies", + "name": "topojson-client", + "from": "3.1.0", + "to": "3.1.0", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -405,7 +413,7 @@ "from": "1.4.0", "to": "1.4.0", "strategy": "installed", - "generatedAt": "2025-10-15T07:57:13.225Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", @@ -413,23 +421,31 @@ "from": "11.1.0", "to": "11.1.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.548Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "dependencies", + "name": "world-atlas", + "from": "2.0.2", + "to": "2.0.2", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "zod", - "from": "3.25.73", + "from": "4.1.11", "to": "4.1.11", "strategy": "installed", - "generatedAt": "2025-10-01T09:40:25.207Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "dependencies", "name": "zustand", - "from": "4.5.7", + "from": "5.0.8", "to": "5.0.8", "strategy": "installed", - "generatedAt": "2025-10-01T09:40:25.207Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -437,15 +453,23 @@ "from": "5.2.1", "to": "5.2.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "@playwright/test", - "from": "1.53.2", - "to": "1.53.2", + "from": "1.56.1", + "to": "1.56.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "devDependencies", + "name": "@types/d3", + "from": "7.4.3", + "to": "7.4.3", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -453,23 +477,31 @@ "from": "20.5.7", "to": "20.5.7", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "@types/react", - "from": "18.3.3", + "from": "19.1.13", "to": "19.1.13", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "@types/react-dom", - "from": "18.3.0", + "from": "19.1.9", "to": "19.1.9", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "devDependencies", + "name": "@types/topojson-client", + "from": "3.1.5", + "to": "3.1.5", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -477,7 +509,7 @@ "from": "10.0.0", "to": "10.0.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -485,7 +517,7 @@ "from": "7.18.0", "to": "7.18.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -493,7 +525,7 @@ "from": "7.18.0", "to": "7.18.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -501,7 +533,7 @@ "from": "10.4.19", "to": "10.4.19", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -509,7 +541,7 @@ "from": "19.1.0-rc.3", "to": "19.1.0-rc.3", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -517,15 +549,15 @@ "from": "8.57.1", "to": "8.57.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "eslint-config-next", - "from": "14.2.32", + "from": "15.5.3", "to": "15.5.3", "strategy": "installed", - "generatedAt": "2025-09-23T10:22:08.630Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -533,7 +565,7 @@ "from": "10.1.5", "to": "10.1.5", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -541,7 +573,7 @@ "from": "2.32.0", "to": "2.32.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -549,7 +581,7 @@ "from": "6.10.2", "to": "6.10.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -557,7 +589,7 @@ "from": "11.1.0", "to": "11.1.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -565,7 +597,7 @@ "from": "5.5.1", "to": "5.5.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -573,7 +605,7 @@ "from": "7.37.5", "to": "7.37.5", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -581,7 +613,7 @@ "from": "4.6.2", "to": "4.6.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -589,7 +621,7 @@ "from": "3.0.1", "to": "3.0.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -597,7 +629,7 @@ "from": "12.1.1", "to": "12.1.1", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -605,7 +637,7 @@ "from": "3.2.0", "to": "3.2.0", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -613,7 +645,7 @@ "from": "9.1.7", "to": "9.1.7", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -621,7 +653,7 @@ "from": "15.5.2", "to": "15.5.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -629,7 +661,7 @@ "from": "8.4.38", "to": "8.4.38", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -637,15 +669,23 @@ "from": "3.6.2", "to": "3.6.2", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "prettier-plugin-tailwindcss", - "from": "0.6.13", + "from": "0.6.14", "to": "0.6.14", "strategy": "installed", - "generatedAt": "2025-09-24T13:59:11.231Z" + "generatedAt": "2025-10-22T12:36:37.962Z" + }, + { + "section": "devDependencies", + "name": "shadcn", + "from": "3.4.1", + "to": "3.4.1", + "strategy": "installed", + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -653,15 +693,15 @@ "from": "0.1.20", "to": "0.1.20", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", "name": "tailwindcss", - "from": "3.4.3", + "from": "4.1.13", "to": "4.1.13", "strategy": "installed", - "generatedAt": "2025-09-24T13:59:11.231Z" + "generatedAt": "2025-10-22T12:36:37.962Z" }, { "section": "devDependencies", @@ -669,6 +709,6 @@ "from": "5.5.4", "to": "5.5.4", "strategy": "installed", - "generatedAt": "2025-09-10T11:50:17.554Z" + "generatedAt": "2025-10-22T12:36:37.962Z" } ] diff --git a/ui/package-lock.json b/ui/package-lock.json index 49aaa1bfe6..1530aa3134 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -36,6 +36,7 @@ "alert": "6.0.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", + "d3": "7.9.0", "date-fns": "4.1.0", "framer-motion": "11.18.2", "intl-messageformat": "10.7.16", @@ -58,17 +59,21 @@ "sharp": "0.33.5", "tailwind-merge": "3.3.1", "tailwindcss-animate": "1.0.7", + "topojson-client": "3.1.0", "tw-animate-css": "1.4.0", "uuid": "11.1.0", + "world-atlas": "2.0.2", "zod": "4.1.11", "zustand": "5.0.8" }, "devDependencies": { "@iconify/react": "5.2.1", "@playwright/test": "1.56.1", + "@types/d3": "7.4.3", "@types/node": "20.5.7", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", + "@types/topojson-client": "3.1.5", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", @@ -722,26 +727,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", - "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cookie": "^0.7.2" - } - }, - "node_modules/@bundled-es-modules/statuses": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", - "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "statuses": "^2.0.1" - } - }, "node_modules/@cfworker/json-schema": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", @@ -971,9 +956,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", + "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", "license": "MIT", "optional": true, "dependencies": { @@ -982,9 +967,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", "license": "MIT", "optional": true, "dependencies": { @@ -1021,9 +1006,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -3376,9 +3361,9 @@ } }, "node_modules/@inquirer/ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.0.tgz", - "integrity": "sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz", + "integrity": "sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==", "dev": true, "license": "MIT", "engines": { @@ -3386,14 +3371,14 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.18", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.18.tgz", - "integrity": "sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==", + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.19.tgz", + "integrity": "sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" + "@inquirer/core": "^10.3.0", + "@inquirer/type": "^3.0.9" }, "engines": { "node": ">=18" @@ -3408,15 +3393,15 @@ } }, "node_modules/@inquirer/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", - "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.0.tgz", + "integrity": "sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", + "@inquirer/ansi": "^1.0.1", + "@inquirer/figures": "^1.0.14", + "@inquirer/type": "^3.0.9", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", @@ -3499,9 +3484,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz", + "integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==", "dev": true, "license": "MIT", "engines": { @@ -3509,9 +3494,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.9.tgz", + "integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==", "dev": true, "license": "MIT", "engines": { @@ -3750,9 +3735,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.1.6.tgz", - "integrity": "sha512-PeXxfo4ls8yql6YdW8qjnZgp1giy7oqJiGjy4j2OSJ7lpkir8n62YpvADDByEh9sPzGLJYh92ZUAh0GNfQ18vA==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.1.10.tgz", + "integrity": "sha512-9srSCb2bSvcvehMgjA2sMMwX0o1VUgPN6ghwm5Fwc9JGAKsQa6n1S4eCwy1h4abuYxwajH5n3spBw+4I2WYbgw==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.15", @@ -3899,9 +3884,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.18.1.tgz", - "integrity": "sha512-d//GE8/Yh7aC3e7p+kZG8JqqEAwwDUmAfvH1quogtbk+ksS6E0RR6toKKESPYYZVre0meqkJb27zb+dhqE9Sgw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz", + "integrity": "sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA==", "dev": true, "license": "MIT", "dependencies": { @@ -3943,9 +3928,9 @@ } }, "node_modules/@mswjs/interceptors": { - "version": "0.39.7", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.7.tgz", - "integrity": "sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", + "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5991,23 +5976,23 @@ } }, "node_modules/@react-aria/grid": { - "version": "3.14.4", - "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.4.tgz", - "integrity": "sha512-l1FLQNKnoHpY4UClUTPUV0AqJ5bfAULEE0ErY86KznWLd+Hqzo7mHLqqDV02CDa/8mIUcdoax/MrYYIbPDlOZA==", + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.5.tgz", + "integrity": "sha512-XHw6rgjlTqc85e3zjsWo3U0EVwjN5MOYtrolCKc/lc2ItNdcY3OlMhpsU9+6jHwg/U3VCSWkGvwAz9hg7krd8Q==", "license": "Apache-2.0", "dependencies": { - "@react-aria/focus": "^3.21.1", - "@react-aria/i18n": "^3.12.12", - "@react-aria/interactions": "^3.25.5", + "@react-aria/focus": "^3.21.2", + "@react-aria/i18n": "^3.12.13", + "@react-aria/interactions": "^3.25.6", "@react-aria/live-announcer": "^3.4.4", - "@react-aria/selection": "^3.25.1", - "@react-aria/utils": "^3.30.1", - "@react-stately/collections": "^3.12.7", - "@react-stately/grid": "^3.11.5", - "@react-stately/selection": "^3.20.5", - "@react-types/checkbox": "^3.10.1", - "@react-types/grid": "^3.3.5", - "@react-types/shared": "^3.32.0", + "@react-aria/selection": "^3.26.0", + "@react-aria/utils": "^3.31.0", + "@react-stately/collections": "^3.12.8", + "@react-stately/grid": "^3.11.6", + "@react-stately/selection": "^3.20.6", + "@react-types/checkbox": "^3.10.2", + "@react-types/grid": "^3.3.6", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6015,6 +6000,158 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-aria/grid/node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/focus": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz", + "integrity": "sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/i18n": { + "version": "3.12.13", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.13.tgz", + "integrity": "sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.10.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/selection": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.26.0.tgz", + "integrity": "sha512-ZBH3EfWZ+RfhTj01dH8L17uT7iNbXWS8u77/fUpHgtrm0pwNVhx0TYVnLU1YpazQ/3WVpvWhmBB8sWwD1FlD/g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.2", + "@react-aria/i18n": "^3.12.13", + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-stately/selection": "^3.20.6", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/checkbox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.10.2.tgz", + "integrity": "sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/grid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.6.tgz", + "integrity": "sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-aria/i18n": { "version": "3.12.12", "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.12.tgz", @@ -6098,13 +6235,13 @@ } }, "node_modules/@react-aria/landmark": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.6.tgz", - "integrity": "sha512-dMPBqJWTDAr3Lj5hA+XYDH2PWqtFghYy+y7iq7K5sK/96cub8hZEUjhwn+HGgHsLerPp0dWt293nKupAJnf4Vw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.7.tgz", + "integrity": "sha512-t8c610b8hPLS6Vwv+rbuSyljZosI1s5+Tosfa0Fk4q7d+Ex6Yj7hLfUFy59GxZAufhUYfGX396fT0gPqAbU1tg==", "license": "Apache-2.0", "dependencies": { - "@react-aria/utils": "^3.30.1", - "@react-types/shared": "^3.32.0", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0", "use-sync-external-store": "^1.4.0" }, @@ -6113,16 +6250,49 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@react-aria/link": { - "version": "3.8.5", - "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.8.5.tgz", - "integrity": "sha512-klhV4roPp5MLRXJv1N+7SXOj82vx4gzVpuwQa3vouA+YI1my46oNzwgtkLGSTvE9OvDqYzPDj2YxFYhMywrkuw==", + "node_modules/@react-aria/landmark/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.25.5", - "@react-aria/utils": "^3.30.1", - "@react-types/link": "^3.6.4", - "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/landmark/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.8.6.tgz", + "integrity": "sha512-7F7UDJnwbU9IjfoAdl6f3Hho5/WB7rwcydUOjUux0p7YVWh/fTjIFjfAGyIir7MJhPapun1D0t97QQ3+8jXVcg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/link": "^3.6.5", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6130,6 +6300,68 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-aria/link/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link/node_modules/@react-types/link": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.6.5.tgz", + "integrity": "sha512-+I2s3XWBEvLrzts0GnNeA84mUkwo+a7kLUWoaJkW0TOBDG7my95HFYxF9WnqKye7NgpOkCqz4s3oW96xPdIniQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-aria/listbox": { "version": "3.14.8", "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.8.tgz", @@ -6232,6 +6464,23 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-aria/overlays/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-aria/overlays/node_modules/@react-aria/ssr": { "version": "3.9.10", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", @@ -6247,15 +6496,33 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@react-aria/overlays/node_modules/@react-aria/visually-hidden": { - "version": "3.8.27", - "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.27.tgz", - "integrity": "sha512-hD1DbL3WnjPnCdlQjwe19bQVRAGJyN0Aaup+s7NNtvZUn7AjoEH78jo8TE+L8yM7z/OZUQF26laCfYqeIwWn4g==", + "node_modules/@react-aria/overlays/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.25.5", - "@react-aria/utils": "^3.30.1", - "@react-types/shared": "^3.32.0", + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/overlays/node_modules/@react-aria/visually-hidden": { + "version": "3.8.28", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.28.tgz", + "integrity": "sha512-KRRjbVVob2CeBidF24dzufMxBveEUtUu7IM+hpdZKB+gxVROoh4XRLPv9SFmaH89Z7D9To3QoykVZoWD0lan6Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6343,16 +6610,16 @@ } }, "node_modules/@react-aria/spinbutton": { - "version": "3.6.18", - "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.18.tgz", - "integrity": "sha512-dnmh7sNsprhYTpqCJhcuc9QJ9C/IG/o9TkgW5a9qcd2vS+dzEgqAiJKIMbJFG9kiJymv2NwIPysF12IWix+J3A==", + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.19.tgz", + "integrity": "sha512-xOIXegDpts9t3RSHdIN0iYQpdts0FZ3LbpYJIYVvdEHo9OpDS+ElnDzCGtwZLguvZlwc5s1LAKuKopDUsAEMkw==", "license": "Apache-2.0", "dependencies": { - "@react-aria/i18n": "^3.12.12", + "@react-aria/i18n": "^3.12.13", "@react-aria/live-announcer": "^3.4.4", - "@react-aria/utils": "^3.30.1", - "@react-types/button": "^3.14.0", - "@react-types/shared": "^3.32.0", + "@react-aria/utils": "^3.31.0", + "@react-types/button": "^3.14.1", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6360,6 +6627,80 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-aria/spinbutton/node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/i18n": { + "version": "3.12.13", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.13.tgz", + "integrity": "sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.10.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-types/button": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.14.1.tgz", + "integrity": "sha512-D8C4IEwKB7zEtiWYVJ3WE/5HDcWlze9mLWQ5hfsBfpePyWCgO3bT/+wjb/7pJvcAocrkXo90QrMm85LcpBtrpg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-aria/ssr": { "version": "3.9.4", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", @@ -6419,15 +6760,65 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@react-aria/table/node_modules/@react-aria/visually-hidden": { - "version": "3.8.27", - "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.27.tgz", - "integrity": "sha512-hD1DbL3WnjPnCdlQjwe19bQVRAGJyN0Aaup+s7NNtvZUn7AjoEH78jo8TE+L8yM7z/OZUQF26laCfYqeIwWn4g==", + "node_modules/@react-aria/table/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.25.5", - "@react-aria/utils": "^3.30.1", - "@react-types/shared": "^3.32.0", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table/node_modules/@react-aria/visually-hidden": { + "version": "3.8.28", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.28.tgz", + "integrity": "sha512-KRRjbVVob2CeBidF24dzufMxBveEUtUu7IM+hpdZKB+gxVROoh4XRLPv9SFmaH89Z7D9To3QoykVZoWD0lan6Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6497,16 +6888,16 @@ } }, "node_modules/@react-aria/toggle": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.12.1.tgz", - "integrity": "sha512-XaFiRs1KEcIT6bTtVY/KTQxw4kinemj/UwXw2iJTu9XS43hhJ/9cvj8KzNGrKGqaxTpOYj62TnSHZbSiFViHDA==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.12.2.tgz", + "integrity": "sha512-g25XLYqJuJpt0/YoYz2Rab8ax+hBfbssllcEFh0v0jiwfk2gwTWfRU9KAZUvxIqbV8Nm8EBmrYychDpDcvW1kw==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.25.5", - "@react-aria/utils": "^3.30.1", - "@react-stately/toggle": "^3.9.1", - "@react-types/checkbox": "^3.10.1", - "@react-types/shared": "^3.32.0", + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-stately/toggle": "^3.9.2", + "@react-types/checkbox": "^3.10.2", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -6514,6 +6905,83 @@ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-stately/toggle": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.9.2.tgz", + "integrity": "sha512-dOxs9wrVXHUmA7lc8l+N9NbTJMAaXcYsnNGsMwfXIXQ3rdq+IjWGNYJ52UmNQyRYFcg0jrzRrU16TyGbNjOdNQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/checkbox": "^3.10.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-types/checkbox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.10.2.tgz", + "integrity": "sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-aria/toolbar": { "version": "3.0.0-beta.20", "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.20.tgz", @@ -6704,21 +7172,46 @@ } }, "node_modules/@react-stately/grid": { - "version": "3.11.5", - "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.5.tgz", - "integrity": "sha512-4cNjGYaNkcVS2wZoNHUrMRICBpkHStYw57EVemP7MjiWEVu53kzPgR1Iwmti2WFCpi1Lwu0qWNeCfzKpXW4BTg==", + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.6.tgz", + "integrity": "sha512-vWPAkzpeTIsrurHfMubzMuqEw7vKzFhIJeEK5sEcLunyr1rlADwTzeWrHNbPMl66NAIAi70Dr1yNq+kahQyvMA==", "license": "Apache-2.0", "dependencies": { - "@react-stately/collections": "^3.12.7", - "@react-stately/selection": "^3.20.5", - "@react-types/grid": "^3.3.5", - "@react-types/shared": "^3.32.0", + "@react-stately/collections": "^3.12.8", + "@react-stately/selection": "^3.20.6", + "@react-types/grid": "^3.3.6", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@react-stately/grid/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-types/grid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.6.tgz", + "integrity": "sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@react-stately/list": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.13.0.tgz", @@ -6797,31 +7290,113 @@ } }, "node_modules/@react-stately/select": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.7.1.tgz", - "integrity": "sha512-vZt4j9yVyOTWWJoP9plXmYaPZH2uMxbjcGMDbiShwsFiK8C2m9b3Cvy44TZehfzCWzpMVR/DYxEYuonEIGA82Q==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.8.0.tgz", + "integrity": "sha512-A721nlt0DSCDit0wKvhcrXFTG5Vv1qkEVkeKvobmETZy6piKvwh0aaN8iQno5AFuZaj1iOZeNjZ/20TsDJR/4A==", "license": "Apache-2.0", "dependencies": { - "@react-stately/form": "^3.2.1", - "@react-stately/list": "^3.13.0", - "@react-stately/overlays": "^3.6.19", - "@react-types/select": "^3.10.1", - "@react-types/shared": "^3.32.0", + "@react-stately/form": "^3.2.2", + "@react-stately/list": "^3.13.1", + "@react-stately/overlays": "^3.6.20", + "@react-stately/utils": "^3.10.8", + "@react-types/select": "^3.11.0", + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@react-stately/selection": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.5.tgz", - "integrity": "sha512-YezWUNEn2pz5mQlbhmngiX9HqQsruLSXlkrAzB1DD6aliGrUvPKufTTGCixOaB8KVeCamdiFAgx1WomNplzdQA==", + "node_modules/@react-stately/select/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", "license": "Apache-2.0", "dependencies": { - "@react-stately/collections": "^3.12.7", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/form": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.2.2.tgz", + "integrity": "sha512-soAheOd7oaTO6eNs6LXnfn0tTqvOoe3zN9FvtIhhrErKz9XPc5sUmh3QWwR45+zKbitOi1HOjfA/gifKhZcfWw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/list": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.13.1.tgz", + "integrity": "sha512-eHaoauh21twbcl0kkwULhVJ+CzYcy1jUjMikNVMHOQdhr4WIBdExf7PmSgKHKqsSPhpGg6IpTCY2dUX3RycjDg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.8", + "@react-stately/selection": "^3.20.6", "@react-stately/utils": "^3.10.8", - "@react-types/shared": "^3.32.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/overlays": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.20.tgz", + "integrity": "sha512-YAIe+uI8GUXX8F/0Pzr53YeC5c/bjqbzDFlV8NKfdlCPa6+Jp4B/IlYVjIooBj9+94QvbQdjylegvYWK/iPwlg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/overlays": "^3.9.2", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/overlays": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.2.tgz", + "integrity": "sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.20.6", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.6.tgz", + "integrity": "sha512-a0bjuP2pJYPKEiedz2Us1W1aSz0iHRuyeQEdBOyL6Z6VUa6hIMq9H60kvseir2T85cOa4QggizuRV7mcO6bU5w==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.8", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -7053,13 +7628,25 @@ } }, "node_modules/@react-types/dialog": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.21.tgz", - "integrity": "sha512-jF1gN4bvwYamsLjefaFDnaSKxTa3Wtvn5f7WLjNVZ8ICVoiMBMdUJXTlPQHAL4YWqtCj4hK/3uimR1E+Pwd7Xw==", + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.22.tgz", + "integrity": "sha512-smSvzOcqKE196rWk0oqJDnz+ox5JM5+OT0PmmJXiUD4q7P5g32O6W5Bg7hMIFUI9clBtngo8kLaX2iMg+GqAzg==", "license": "Apache-2.0", "dependencies": { - "@react-types/overlays": "^3.9.1", - "@react-types/shared": "^3.32.0" + "@react-types/overlays": "^3.9.2", + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog/node_modules/@react-types/overlays": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.2.tgz", + "integrity": "sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -7102,12 +7689,12 @@ } }, "node_modules/@react-types/listbox": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.3.tgz", - "integrity": "sha512-ONgror9uyGmIer5XxpRRNcc8QFVWiOzINrMKyaS8G4l3aP52ZwYpRfwMAVtra8lkVNvXDmO7hthPZkB6RYdNOA==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.4.tgz", + "integrity": "sha512-p4YEpTl/VQGrqVE8GIfqTS5LkT5jtjDTbVeZgrkPnX/fiPhsfbTPiZ6g0FNap4+aOGJFGEEZUv2q4vx+rCORww==", "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.32.0" + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -7175,12 +7762,12 @@ } }, "node_modules/@react-types/select": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.10.1.tgz", - "integrity": "sha512-teANUr1byOzGsS/r2j7PatV470JrOhKP8En9lscfnqW5CeUghr+0NxkALnPkiEhCObi/Vu8GIcPareD0HNhtFA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.11.0.tgz", + "integrity": "sha512-SzIsMFVPCbXE1Z1TLfpdfiwJ1xnIkcL1/CjGilmUKkNk5uT7rYX1xCJqWCjXI0vAU1xM4Qn+T3n8de4fw6HRBg==", "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.32.0" + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -7196,24 +7783,24 @@ } }, "node_modules/@react-types/slider": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.8.1.tgz", - "integrity": "sha512-WxiQWj6iQr5Uft0/KcB9XSr361XnyTmL6eREZZacngA9CjPhRWYP3BRDPcCTuP7fj9Yi4QKMrryyjHqMHP8OKQ==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.8.2.tgz", + "integrity": "sha512-MQYZP76OEOYe7/yA2To+Dl0LNb0cKKnvh5JtvNvDnAvEprn1RuLiay8Oi/rTtXmc2KmBa4VdTcsXsmkbbkeN2Q==", "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.32.0" + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/switch": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.14.tgz", - "integrity": "sha512-M8kIv97i+ejCel4Ho+Y7tDbpOehymGwPA4ChxibeyD32+deyxu5B6BXxgKiL3l+oTLQ8ihLo3sRESdPFw8vpQg==", + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.15.tgz", + "integrity": "sha512-r/ouGWQmIeHyYSP1e5luET+oiR7N7cLrAlWsrAfYRWHxqXOSNQloQnZJ3PLHrKFT02fsrQhx2rHaK2LfKeyN3A==", "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.32.0" + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -7233,12 +7820,12 @@ } }, "node_modules/@react-types/tabs": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.18.tgz", - "integrity": "sha512-yX/AVlGS7VXCuy2LSm8y8nxUrKVBgnLv+FrtkLqf6jUMtD4KP3k1c4+GPHeScR0HcYzCQF7gCF3Skba1RdYoug==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.19.tgz", + "integrity": "sha512-fE+qI43yR5pAMpeqPxGqQq9jDHXEPqXskuxNHERMW0PYMdPyem2Cw6goc5F4qeZO3Hf6uPZgHkvJz2OAq7TbBw==", "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.32.0" + "@react-types/shared": "^3.32.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -7277,9 +7864,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", - "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.14.0.tgz", + "integrity": "sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==", "dev": true, "license": "MIT" }, @@ -7756,12 +8343,44 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } }, "node_modules/@types/d3-array": { "version": "3.2.2", @@ -7769,18 +8388,128 @@ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -7796,6 +8525,27 @@ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", @@ -7805,6 +8555,20 @@ "@types/d3-time": "*" } }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-shape": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", @@ -7820,12 +8584,40 @@ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT" }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -7850,6 +8642,13 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -7932,6 +8731,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/topojson-client": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz", + "integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "node_modules/@types/topojson-specification": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz", + "integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -8543,9 +9363,9 @@ } }, "node_modules/ansis": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", - "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", "dev": true, "license": "ISC", "engines": { @@ -8835,9 +9655,9 @@ } }, "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", + "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", "dev": true, "license": "MPL-2.0", "engines": { @@ -8902,9 +9722,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", - "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.19.tgz", + "integrity": "sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8956,9 +9776,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", "dev": true, "funding": [ { @@ -8976,9 +9796,9 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", + "baseline-browser-mapping": "^2.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, @@ -9072,9 +9892,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001745", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", - "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", "funding": [ { "type": "opencollective", @@ -9417,13 +10237,12 @@ } }, "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 10" } }, "node_modules/compute-scroll-into-view": { @@ -9440,12 +10259,12 @@ "license": "MIT" }, "node_modules/console-table-printer": { - "version": "2.14.6", - "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.6.tgz", - "integrity": "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz", + "integrity": "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==", "license": "MIT", "dependencies": { - "simple-wcswidth": "^1.0.1" + "simple-wcswidth": "^1.1.2" } }, "node_modules/content-disposition": { @@ -9572,6 +10391,47 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -9584,6 +10444,43 @@ "node": ">=12" } }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -9593,6 +10490,77 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -9602,6 +10570,32 @@ "node": ">=12" } }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -9611,6 +10605,27 @@ "node": ">=12" } }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -9632,6 +10647,33 @@ "node": ">=12" } }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -9648,6 +10690,28 @@ "node": ">=12" } }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -9693,6 +10757,41 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9892,6 +10991,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9912,9 +11020,9 @@ } }, "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -9986,9 +11094,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", - "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -10014,15 +11122,15 @@ } }, "node_modules/eciesjs": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.15.tgz", - "integrity": "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.16.tgz", + "integrity": "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==", "dev": true, "license": "MIT", "dependencies": { - "@ecies/ciphers": "^0.2.3", + "@ecies/ciphers": "^0.2.4", "@noble/ciphers": "^1.3.0", - "@noble/curves": "^1.9.1", + "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" }, "engines": { @@ -10039,9 +11147,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.223", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz", - "integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==", + "version": "1.5.238", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.238.tgz", + "integrity": "sha512-khBdc+w/Gv+cS8e/Pbnaw/FXcBUeKrRVik9IxfXtgREOWyJhR4tj43n3amkVogJ/yeQUqzkrZcFhtIxIdqmmcQ==", "dev": true, "license": "ISC" }, @@ -11254,9 +12362,9 @@ "license": "Apache-2.0" }, "node_modules/fast-equals": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.0.tgz", - "integrity": "sha512-xwP+dG/in/nJelMOUEQBiIYeOoHKihWPB2sNZ8ZeDbZFoGb1OwTGMggGRgg6CRitNx7kmHgtIz2dOHDQ8Ap7Bw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -11630,6 +12738,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11756,9 +12874,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12138,7 +13256,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -12491,14 +13608,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -12864,9 +13982,9 @@ } }, "node_modules/jiti": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", - "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -13027,9 +14145,9 @@ } }, "node_modules/langsmith": { - "version": "0.3.71", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.71.tgz", - "integrity": "sha512-xl00JZso7J3OaurUQ+seT2qRJ34OGZXYAvCYj3vNC3TB+JOcdcYZ1uLvENqOloKB8VCiADh1eZ0FG3Cj/cy2FQ==", + "version": "0.3.74", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.74.tgz", + "integrity": "sha512-ZuW3Qawz8w88XcuCRH91yTp6lsdGuwzRqZ5J0Hf5q/AjMz7DwcSv0MkE6V5W+8hFMI850QZN2Wlxwm3R9lHlZg==", "license": "MIT", "dependencies": { "@types/uuid": "^10.0.0", @@ -13397,6 +14515,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/lint-staged/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", @@ -14432,20 +15560,18 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.11.3.tgz", - "integrity": "sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==", + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.11.6.tgz", + "integrity": "sha512-MCYMykvmiYScyUm7I6y0VCxpNq1rgd5v7kG8ks5dKtvmxRUUPjribX6mUoUNBbM5/3PhUyoelEWiKXGOz84c+w==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@bundled-es-modules/cookie": "^2.0.1", - "@bundled-es-modules/statuses": "^1.0.1", "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.39.1", + "@mswjs/interceptors": "^0.40.0", "@open-draft/deferred-promise": "^2.2.0", - "@types/cookie": "^0.6.0", "@types/statuses": "^2.0.4", + "cookie": "^1.0.2", "graphql": "^16.8.1", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", @@ -14453,6 +15579,7 @@ "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.7.0", + "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^4.26.1", @@ -14477,6 +15604,16 @@ } } }, + "node_modules/msw/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/msw/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -14528,9 +15665,9 @@ } }, "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -15132,9 +16269,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", "dev": true, "license": "MIT" }, @@ -15178,9 +16315,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", - "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.2.tgz", + "integrity": "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -15555,9 +16692,9 @@ } }, "node_modules/package-manager-detector": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", - "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", "dev": true, "license": "MIT" }, @@ -16581,13 +17718,13 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -16705,6 +17842,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -16767,6 +17910,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -16857,7 +18006,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sax": { @@ -16882,9 +18030,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17591,9 +18739,9 @@ } }, "node_modules/string-width/node_modules/emoji-regex": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", - "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -17808,18 +18956,18 @@ } }, "node_modules/style-to-js": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", - "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz", + "integrity": "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.9" + "style-to-object": "1.0.11" } }, "node_modules/style-to-object": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", - "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.11.tgz", + "integrity": "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==", "license": "MIT", "dependencies": { "inline-style-parser": "0.2.4" @@ -17956,9 +19104,9 @@ } }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" @@ -18080,22 +19228,22 @@ } }, "node_modules/tldts": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", - "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.16" + "tldts-core": "^7.0.17" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", - "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", "dev": true, "license": "MIT" }, @@ -18122,6 +19270,26 @@ "node": ">=0.6" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -18405,9 +19573,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -18459,9 +19627,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -18667,9 +19835,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -18879,6 +20047,12 @@ "node": ">=0.10.0" } }, + "node_modules/world-atlas": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/world-atlas/-/world-atlas-2.0.2.tgz", + "integrity": "sha512-IXfV0qwlKXpckz1FhwXVwKRjiIhOnWttOskm5CtxMsjgE/MXAYRHWJqgXOpM8IkcPBoXnyTU5lFHcYa5ChG0LQ==", + "license": "ISC" + }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", diff --git a/ui/package.json b/ui/package.json index c491fb4ba7..ee103a6f45 100644 --- a/ui/package.json +++ b/ui/package.json @@ -50,6 +50,7 @@ "alert": "6.0.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", + "d3": "7.9.0", "date-fns": "4.1.0", "framer-motion": "11.18.2", "intl-messageformat": "10.7.16", @@ -72,17 +73,21 @@ "sharp": "0.33.5", "tailwind-merge": "3.3.1", "tailwindcss-animate": "1.0.7", + "topojson-client": "3.1.0", "tw-animate-css": "1.4.0", "uuid": "11.1.0", + "world-atlas": "2.0.2", "zod": "4.1.11", "zustand": "5.0.8" }, "devDependencies": { "@iconify/react": "5.2.1", + "@types/d3": "7.4.3", "@playwright/test": "1.56.1", "@types/node": "20.5.7", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", + "@types/topojson-client": "3.1.5", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", diff --git a/ui/styles/globals.css b/ui/styles/globals.css index 8209e59934..3265d97c4f 100644 --- a/ui/styles/globals.css +++ b/ui/styles/globals.css @@ -1,8 +1,59 @@ @import "tailwindcss"; @config "../tailwind.config.js"; +@theme { + /* Chart Severity Colors - Dark Theme */ + --chart-info: #2e51b2; + --chart-warning: #fdd34f; + --chart-warning-emphasis: #ff7d19; + --chart-danger: #ff3077; + --chart-danger-emphasis: #971348; + + /* Chart Status Colors */ + --chart-success-color: #86da26; + --chart-fail: #db2b49; + + /* Chart Radar Colors */ + --chart-radar-primary: #b51c80; + --chart-radar-primary-rgb: 181 28 128; + + /* Chart Provider Colors */ + --chart-provider-aws: #ff9900; + --chart-provider-azure: #00bcd4; + --chart-provider-google: #EA4335; + + /* Chart UI Colors - Dark Theme (defaults) */ + --chart-text-primary: #ffffff; + --chart-text-secondary: #94a3b8; + --chart-border: #475569; + --chart-border-emphasis: #334155; + --chart-background: #1e293b; + + /* Chart Alert Colors */ + --chart-alert-bg: #432232; + --chart-alert-text: #f54280; +} + @layer base { :root { + /* Light Theme Chart Colors */ + --chart-info: #1e40af; + --chart-warning: #d97706; + --chart-warning-emphasis: #dc2626; + --chart-danger: #dc2626; + --chart-danger-emphasis: #991b1b; + --chart-success-color: #16a34a; + --chart-fail: #dc2626; + --chart-radar-primary: #9d174d; + --chart-text-primary: #1f2937; + --chart-text-secondary: #6b7280; + --chart-border: #d1d5db; + --chart-border-emphasis: #9ca3af; + --chart-background: #f9fafb; + --chart-alert-bg: #fecdd3; + --chart-alert-text: #be123c; + + /* Chart HSL values */ --chart-success: 146 80% 35%; --chart-fail: 339 90% 51%; --chart-muted: 45 93% 47%; @@ -17,6 +68,24 @@ } .dark { + /* Dark Theme Chart Colors */ + --chart-info: #2e51b2; + --chart-warning: #fdd34f; + --chart-warning-emphasis: #ff7d19; + --chart-danger: #ff3077; + --chart-danger-emphasis: #971348; + --chart-success-color: #86da26; + --chart-fail: #db2b49; + --chart-radar-primary: #b51c80; + --chart-text-primary: #ffffff; + --chart-text-secondary: #94a3b8; + --chart-border: #475569; + --chart-border-emphasis: #334155; + --chart-background: #1e293b; + --chart-alert-bg: #432232; + --chart-alert-text: #f54280; + + /* Chart HSL values */ --chart-success: 146 80% 35%; --chart-fail: 339 90% 51%; --chart-muted: 45 93% 47%; @@ -56,6 +125,7 @@ transform-box: fill-box; transform-origin: center; } + } @layer base { From 9f372902addb5a8394d220e1f1b24d2857cbf80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 22 Oct 2025 15:59:56 +0200 Subject: [PATCH 37/57] feat(threatscore): support compliance pdf reporting (#8867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sergio Garcia Co-authored-by: alejandrobailo Co-authored-by: Víctor Fernández Poyatos --- .gitignore | 3 + api/CHANGELOG.md | 1 + api/poetry.lock | 529 ++++++- api/pyproject.toml | 4 +- api/src/backend/api/v1/views.py | 61 + .../tasks/assets/fonts/FiraCode-Regular.ttf | Bin 0 -> 188500 bytes .../assets/fonts/PlusJakartaSans-Regular.ttf | Bin 0 -> 94760 bytes .../backend/tasks/assets/img/prowler_logo.png | Bin 0 -> 24198 bytes api/src/backend/tasks/jobs/export.py | 11 +- api/src/backend/tasks/jobs/report.py | 1332 +++++++++++++++++ api/src/backend/tasks/tasks.py | 34 +- api/src/backend/tasks/tests/test_export.py | 8 +- api/src/backend/tasks/tests/test_report.py | 957 ++++++++++++ api/src/backend/tasks/tests/test_tasks.py | 186 ++- ui/CHANGELOG.md | 1 + ui/actions/scans/scans.ts | 42 + .../compliance/[compliancetitle]/page.tsx | 27 +- .../threatscore-download-button.tsx | 45 + ui/app/(prowler)/compliance/page.tsx | 107 +- .../compliance-header/data-compliance.tsx | 4 +- ui/components/compliance/index.ts | 2 + .../compliance/threatscore-badge.tsx | 148 ++ ui/components/compliance/threatscore-logo.tsx | 79 + ui/lib/compliance/threatscore-calculator.ts | 69 + ui/lib/helper.ts | 49 +- 25 files changed, 3587 insertions(+), 112 deletions(-) create mode 100644 api/src/backend/tasks/assets/fonts/FiraCode-Regular.ttf create mode 100644 api/src/backend/tasks/assets/fonts/PlusJakartaSans-Regular.ttf create mode 100644 api/src/backend/tasks/assets/img/prowler_logo.png create mode 100644 api/src/backend/tasks/jobs/report.py create mode 100644 api/src/backend/tasks/tests/test_report.py create mode 100644 ui/app/(prowler)/compliance/[compliancetitle]/threatscore-download-button.tsx create mode 100644 ui/components/compliance/threatscore-badge.tsx create mode 100644 ui/components/compliance/threatscore-logo.tsx create mode 100644 ui/lib/compliance/threatscore-calculator.ts diff --git a/.gitignore b/.gitignore index 4b39b18b83..74dcdac26a 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ CLAUDE.md # MCP Server mcp_server/prowler_mcp_server/prowler_app/server.py mcp_server/prowler_mcp_server/prowler_app/utils/schema.yaml + +# Compliance report +*.pdf diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index bdbcdcda6d..be3eacf644 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler API** are documented in this file. - API Key support [(#8805)](https://github.com/prowler-cloud/prowler/pull/8805) - SAML role mapping protection for single-admin tenants to prevent accidental lockout [(#8882)](https://github.com/prowler-cloud/prowler/pull/8882) - Support for `passed_findings` and `total_findings` fields in compliance requirement overview for accurate Prowler ThreatScore calculation [(#8582)](https://github.com/prowler-cloud/prowler/pull/8582) +- PDF reporting for Prowler ThreatScore [(#8867)](https://github.com/prowler-cloud/prowler/pull/8867) - Database read replica support [(#8869)](https://github.com/prowler-cloud/prowler/pull/8869) - Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) - Add `provider_id__in` filter support to findings and findings severity overview endpoints [(#8951)](https://github.com/prowler-cloud/prowler/pull/8951) diff --git a/api/poetry.lock b/api/poetry.lock index 61929f3bdc..40313d9fa5 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1256,6 +1256,98 @@ files = [ {file = "contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869"}, ] +[[package]] +name = "contourpy" +version = "1.3.3" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, +] + +[package.dependencies] +numpy = ">=1.25" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" version = "7.5.4" @@ -1390,6 +1482,22 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "dash" version = "3.1.1" @@ -2120,6 +2228,87 @@ werkzeug = ">=3.1.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "fonttools" +version = "4.60.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, + {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, + {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, + {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, + {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, + {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, + {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, + {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, + {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, + {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, + {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, + {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, + {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, + {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, + {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, + {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, + {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + [[package]] name = "freezegun" version = "1.5.1" @@ -2787,6 +2976,117 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "kiwisolver" +version = "1.4.9" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, +] + [[package]] name = "kombu" version = "5.5.4" @@ -3137,6 +3437,85 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] tests = ["pytest", "simplejson"] +[[package]] +name = "matplotlib" +version = "3.10.6" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, + {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, + {file = "matplotlib-3.10.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fa4c43d6bfdbfec09c733bca8667de11bfa4970e8324c471f3a3632a0301c15"}, + {file = "matplotlib-3.10.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea117a9c1627acaa04dbf36265691921b999cbf515a015298e54e1a12c3af837"}, + {file = "matplotlib-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08fc803293b4e1694ee325896030de97f74c141ccff0be886bb5915269247676"}, + {file = "matplotlib-3.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:2adf92d9b7527fbfb8818e050260f0ebaa460f79d61546374ce73506c9421d09"}, + {file = "matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f"}, + {file = "matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76"}, + {file = "matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6"}, + {file = "matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f"}, + {file = "matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce"}, + {file = "matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e"}, + {file = "matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951"}, + {file = "matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347"}, + {file = "matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75"}, + {file = "matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95"}, + {file = "matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb"}, + {file = "matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07"}, + {file = "matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b"}, + {file = "matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa"}, + {file = "matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a"}, + {file = "matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf"}, + {file = "matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a"}, + {file = "matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110"}, + {file = "matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2"}, + {file = "matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18"}, + {file = "matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6"}, + {file = "matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f"}, + {file = "matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27"}, + {file = "matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833"}, + {file = "matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa"}, + {file = "matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706"}, + {file = "matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e"}, + {file = "matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5"}, + {file = "matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899"}, + {file = "matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c"}, + {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438"}, + {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453"}, + {file = "matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47"}, + {file = "matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98"}, + {file = "matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a"}, + {file = "matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b"}, + {file = "matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c"}, + {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3"}, + {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf"}, + {file = "matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a"}, + {file = "matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3"}, + {file = "matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:13fcd07ccf17e354398358e0307a1f53f5325dca22982556ddb9c52837b5af41"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:470fc846d59d1406e34fa4c32ba371039cd12c2fe86801159a965956f2575bd1"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7173f8551b88f4ef810a94adae3128c2530e0d07529f7141be7f8d8c365f051"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb"}, + {file = "matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "mccabe" version = "0.7.0" @@ -3857,6 +4236,131 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "pillow" +version = "11.3.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions ; python_version < \"3.10\""] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.3.8" @@ -5016,6 +5520,29 @@ attrs = ">=22.2.0" rpds-py = ">=0.7.0" typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} +[[package]] +name = "reportlab" +version = "4.4.4" +description = "The Reportlab Toolkit" +optional = false +python-versions = "<4,>=3.9" +groups = ["main"] +files = [ + {file = "reportlab-4.4.4-py3-none-any.whl", hash = "sha256:299b3b0534e7202bb94ed2ddcd7179b818dcda7de9d8518a57c85a58a1ebaadb"}, + {file = "reportlab-4.4.4.tar.gz", hash = "sha256:cb2f658b7f4a15be2cc68f7203aa67faef67213edd4f2d4bdd3eb20dab75a80d"}, +] + +[package.dependencies] +charset-normalizer = "*" +pillow = ">=9.0.0" + +[package.extras] +accel = ["rl_accel (>=0.9.0,<1.1)"] +bidi = ["rlbidi"] +pycairo = ["freetype-py (>=2.3.0,<2.4)", "rlPyCairo (>=0.2.0,<1)"] +renderpm = ["rl_renderPM (>=4.0.3,<4.1)"] +shaping = ["uharfbuzz"] + [[package]] name = "requests" version = "2.32.5" @@ -6259,4 +6786,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "03442fd4673006c5a74374f90f53621fd1c9d117279fe6cc0355ef833eb7f9bb" +content-hash = "3c9164d668d37d6373eb5200bbe768232ead934d9312b9c68046b1df922789f3" diff --git a/api/pyproject.toml b/api/pyproject.toml index d7ef074e36..b1cd4af120 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -33,7 +33,9 @@ dependencies = [ "xmlsec==1.3.14", "h2 (==4.3.0)", "markdown (>=3.9,<4.0)", - "drf-simple-apikey (==2.2.1)" + "drf-simple-apikey (==2.2.1)", + "matplotlib (>=3.10.6,<4.0.0)", + "reportlab (>=4.4.4,<5.0.0)" ] description = "Prowler's API (Django/DRF)" license = "Apache-2.0" diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 6e561ff969..d24c68ccc4 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -1593,6 +1593,25 @@ class ProviderViewSet(BaseRLSViewSet): }, request=None, ), + threatscore=extend_schema( + tags=["Scan"], + summary="Retrieve threatscore report", + description="Download a specific threatscore report (e.g., 'prowler_threatscore_aws') as a PDF file.", + request=None, + responses={ + 200: OpenApiResponse( + description="PDF file containing the threatscore report" + ), + 202: OpenApiResponse(description="The task is in progress"), + 401: OpenApiResponse( + description="API key missing or user not Authenticated" + ), + 403: OpenApiResponse(description="There is a problem with credentials"), + 404: OpenApiResponse( + description="The scan has no threatscore reports, or the threatscore report generation task has not started yet" + ), + }, + ), ) @method_decorator(CACHE_DECORATOR, name="list") @method_decorator(CACHE_DECORATOR, name="retrieve") @@ -1649,6 +1668,9 @@ class ScanViewSet(BaseRLSViewSet): if hasattr(self, "response_serializer_class"): return self.response_serializer_class return ScanComplianceReportSerializer + elif self.action == "threatscore": + if hasattr(self, "response_serializer_class"): + return self.response_serializer_class return super().get_serializer_class() def partial_update(self, request, *args, **kwargs): @@ -1880,6 +1902,45 @@ class ScanViewSet(BaseRLSViewSet): content, filename = loader return self._serve_file(content, filename, "text/csv") + @action( + detail=True, + methods=["get"], + url_name="threatscore", + ) + def threatscore(self, request, pk=None): + scan = self.get_object() + running_resp = self._get_task_status(scan) + if running_resp: + return running_resp + + if not scan.output_location: + return Response( + { + "detail": "The scan has no reports, or the threatscore report generation task has not started yet." + }, + status=status.HTTP_404_NOT_FOUND, + ) + + if scan.output_location.startswith("s3://"): + bucket = env.str("DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET", "") + key_prefix = scan.output_location.removeprefix(f"s3://{bucket}/") + prefix = os.path.join( + os.path.dirname(key_prefix), + "threatscore", + "*_threatscore_report.pdf", + ) + loader = self._load_file(prefix, s3=True, bucket=bucket, list_objects=True) + else: + base = os.path.dirname(scan.output_location) + pattern = os.path.join(base, "threatscore", "*_threatscore_report.pdf") + loader = self._load_file(pattern, s3=False) + + if isinstance(loader, Response): + return loader + + content, filename = loader + return self._serve_file(content, filename, "application/pdf") + def create(self, request, *args, **kwargs): input_serializer = self.get_serializer(data=request.data) input_serializer.is_valid(raise_exception=True) diff --git a/api/src/backend/tasks/assets/fonts/FiraCode-Regular.ttf b/api/src/backend/tasks/assets/fonts/FiraCode-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3a57209a97f2880b3d5f643c75999a8f777059eb GIT binary patch literal 188500 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 literal 0 HcmV?d00001 diff --git a/api/src/backend/tasks/assets/fonts/PlusJakartaSans-Regular.ttf b/api/src/backend/tasks/assets/fonts/PlusJakartaSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f43b5f4356734bedcc05588f4af61732009cd231 GIT binary patch literal 94760 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 W>vxPhzMfG8S?uZR=d#Wzp$P!=!(F!k literal 0 HcmV?d00001 diff --git a/api/src/backend/tasks/jobs/export.py b/api/src/backend/tasks/jobs/export.py index 782e06e2b9..f9a4020225 100644 --- a/api/src/backend/tasks/jobs/export.py +++ b/api/src/backend/tasks/jobs/export.py @@ -229,7 +229,7 @@ def _upload_to_s3(tenant_id: str, zip_path: str, scan_id: str) -> str | None: def _generate_output_directory( output_directory, prowler_provider: object, tenant_id: str, scan_id: str -) -> tuple[str, str]: +) -> tuple[str, str, str]: """ Generate a file system path for the output directory of a prowler scan. @@ -256,6 +256,7 @@ def _generate_output_directory( >>> _generate_output_directory("/tmp", "aws", "tenant-1234", "scan-5678") '/tmp/tenant-1234/aws/scan-5678/prowler-output-2023-02-15T12:34:56', '/tmp/tenant-1234/aws/scan-5678/compliance/prowler-output-2023-02-15T12:34:56' + '/tmp/tenant-1234/aws/scan-5678/threatscore/prowler-output-2023-02-15T12:34:56' """ # Sanitize the prowler provider name to ensure it is a valid directory name prowler_provider_sanitized = re.sub(r"[^\w\-]", "-", prowler_provider) @@ -276,4 +277,10 @@ def _generate_output_directory( ) os.makedirs("/".join(compliance_path.split("/")[:-1]), exist_ok=True) - return path, compliance_path + threatscore_path = ( + f"{output_directory}/{tenant_id}/{scan_id}/threatscore/prowler-output-" + f"{prowler_provider_sanitized}-{timestamp}" + ) + os.makedirs("/".join(threatscore_path.split("/")[:-1]), exist_ok=True) + + return path, compliance_path, threatscore_path diff --git a/api/src/backend/tasks/jobs/report.py b/api/src/backend/tasks/jobs/report.py new file mode 100644 index 0000000000..fc589f513d --- /dev/null +++ b/api/src/backend/tasks/jobs/report.py @@ -0,0 +1,1332 @@ +import io +import os +from collections import defaultdict +from pathlib import Path +from shutil import rmtree + +import matplotlib.pyplot as plt +from celery.utils.log import get_task_logger +from config.django.base import DJANGO_FINDINGS_BATCH_SIZE, DJANGO_TMP_OUTPUT_DIRECTORY +from django.db.models import Count, Q +from reportlab.lib import colors +from reportlab.lib.enums import TA_CENTER +from reportlab.lib.pagesizes import letter +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import inch +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfgen import canvas +from reportlab.platypus import ( + Image, + PageBreak, + Paragraph, + SimpleDocTemplate, + Spacer, + Table, + TableStyle, +) +from tasks.jobs.export import _generate_output_directory, _upload_to_s3 +from tasks.utils import batched + +from api.db_router import READ_REPLICA_ALIAS +from api.db_utils import rls_transaction +from api.models import Finding, Provider, ScanSummary, StatusChoices +from api.utils import initialize_prowler_provider +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.finding import Finding as FindingOutput + +pdfmetrics.registerFont( + TTFont( + "PlusJakartaSans", + os.path.join( + os.path.dirname(__file__), "../assets/fonts/PlusJakartaSans-Regular.ttf" + ), + ) +) + +pdfmetrics.registerFont( + TTFont( + "FiraCode", + os.path.join(os.path.dirname(__file__), "../assets/fonts/FiraCode-Regular.ttf"), + ) +) + +logger = get_task_logger(__name__) + + +def _create_pdf_styles() -> dict[str, ParagraphStyle]: + """ + Create and return PDF paragraph styles used throughout the report. + + Returns: + dict[str, ParagraphStyle]: A dictionary containing the following styles: + - 'title': Title style with prowler green color + - 'h1': Heading 1 style with blue color and background + - 'h2': Heading 2 style with light blue color + - 'h3': Heading 3 style for sub-headings + - 'normal': Normal text style with left indent + - 'normal_center': Normal text style without indent + """ + styles = getSampleStyleSheet() + prowler_dark_green = colors.Color(0.1, 0.5, 0.2) + + title_style = ParagraphStyle( + "CustomTitle", + parent=styles["Title"], + fontSize=24, + textColor=prowler_dark_green, + spaceAfter=20, + fontName="PlusJakartaSans", + alignment=TA_CENTER, + ) + + h1 = ParagraphStyle( + "CustomH1", + parent=styles["Heading1"], + fontSize=18, + textColor=colors.Color(0.2, 0.4, 0.6), + spaceBefore=20, + spaceAfter=12, + fontName="PlusJakartaSans", + leftIndent=0, + borderWidth=2, + borderColor=colors.Color(0.2, 0.4, 0.6), + borderPadding=8, + backColor=colors.Color(0.95, 0.97, 1.0), + ) + + h2 = ParagraphStyle( + "CustomH2", + parent=styles["Heading2"], + fontSize=14, + textColor=colors.Color(0.3, 0.5, 0.7), + spaceBefore=15, + spaceAfter=8, + fontName="PlusJakartaSans", + leftIndent=10, + borderWidth=1, + borderColor=colors.Color(0.7, 0.8, 0.9), + borderPadding=5, + backColor=colors.Color(0.98, 0.99, 1.0), + ) + + h3 = ParagraphStyle( + "CustomH3", + parent=styles["Heading3"], + fontSize=12, + textColor=colors.Color(0.4, 0.6, 0.8), + spaceBefore=10, + spaceAfter=6, + fontName="PlusJakartaSans", + leftIndent=20, + ) + + normal = ParagraphStyle( + "CustomNormal", + parent=styles["Normal"], + fontSize=10, + textColor=colors.Color(0.2, 0.2, 0.2), + spaceBefore=4, + spaceAfter=4, + leftIndent=30, + fontName="PlusJakartaSans", + ) + + normal_center = ParagraphStyle( + "CustomNormalCenter", + parent=styles["Normal"], + fontSize=10, + textColor=colors.Color(0.2, 0.2, 0.2), + fontName="PlusJakartaSans", + ) + + return { + "title": title_style, + "h1": h1, + "h2": h2, + "h3": h3, + "normal": normal, + "normal_center": normal_center, + } + + +def _create_risk_component(risk_level: int, weight: int, score: int = 0) -> Table: + """ + Create a visual risk component table for the PDF report. + + Args: + risk_level (int): The risk level (0-5), where higher values indicate higher risk. + weight (int): The weight of the risk component. + score (int): The calculated score. Defaults to 0. + + Returns: + Table: A ReportLab Table object with colored cells representing risk, weight, and score. + """ + if risk_level >= 4: + risk_color = colors.Color(0.8, 0.2, 0.2) + elif risk_level >= 3: + risk_color = colors.Color(0.9, 0.6, 0.2) + elif risk_level >= 2: + risk_color = colors.Color(0.9, 0.9, 0.2) + else: + risk_color = colors.Color(0.2, 0.8, 0.2) + + if weight <= 50: + weight_color = colors.Color(0.2, 0.8, 0.2) + elif weight <= 100: + weight_color = colors.Color(0.9, 0.9, 0.2) + else: + weight_color = colors.Color(0.8, 0.2, 0.2) + + score_color = colors.Color(0.4, 0.4, 0.4) + + data = [ + [ + "Risk Level:", + str(risk_level), + "Weight:", + str(weight), + "Score:", + str(score), + ] + ] + + table = Table( + data, + colWidths=[ + 0.8 * inch, + 0.4 * inch, + 0.6 * inch, + 0.4 * inch, + 0.5 * inch, + 0.4 * inch, + ], + ) + + table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (0, 0), colors.Color(0.9, 0.9, 0.9)), + ("BACKGROUND", (1, 0), (1, 0), risk_color), + ("TEXTCOLOR", (1, 0), (1, 0), colors.white), + ("FONTNAME", (1, 0), (1, 0), "FiraCode"), + ("BACKGROUND", (2, 0), (2, 0), colors.Color(0.9, 0.9, 0.9)), + ("BACKGROUND", (3, 0), (3, 0), weight_color), + ("TEXTCOLOR", (3, 0), (3, 0), colors.white), + ("FONTNAME", (3, 0), (3, 0), "FiraCode"), + ("BACKGROUND", (4, 0), (4, 0), colors.Color(0.9, 0.9, 0.9)), + ("BACKGROUND", (5, 0), (5, 0), score_color), + ("TEXTCOLOR", (5, 0), (5, 0), colors.white), + ("FONTNAME", (5, 0), (5, 0), "FiraCode"), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("FONTSIZE", (0, 0), (-1, -1), 10), + ("GRID", (0, 0), (-1, -1), 0.5, colors.black), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 8), + ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ] + ) + ) + + return table + + +def _create_status_component(status: str) -> Table: + """ + Create a visual status component with colored background. + + Args: + status (str): The status value (e.g., "PASS", "FAIL", "MANUAL"). + + Returns: + Table: A ReportLab Table object displaying the status with appropriate color coding. + """ + if status.upper() == "PASS": + status_color = colors.Color(0.2, 0.8, 0.2) + elif status.upper() == "FAIL": + status_color = colors.Color(0.8, 0.2, 0.2) + else: + status_color = colors.Color(0.4, 0.4, 0.4) + + data = [["State:", status.upper()]] + + table = Table(data, colWidths=[0.6 * inch, 0.8 * inch]) + + table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (0, 0), colors.Color(0.9, 0.9, 0.9)), + ("FONTNAME", (0, 0), (0, 0), "PlusJakartaSans"), + ("BACKGROUND", (1, 0), (1, 0), status_color), + ("TEXTCOLOR", (1, 0), (1, 0), colors.white), + ("FONTNAME", (1, 0), (1, 0), "FiraCode"), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("FONTSIZE", (0, 0), (-1, -1), 12), + ("GRID", (0, 0), (-1, -1), 0.5, colors.black), + ("LEFTPADDING", (0, 0), (-1, -1), 8), + ("RIGHTPADDING", (0, 0), (-1, -1), 8), + ("TOPPADDING", (0, 0), (-1, -1), 10), + ("BOTTOMPADDING", (0, 0), (-1, -1), 10), + ] + ) + ) + + return table + + +def _create_section_score_chart( + requirements_list: list[dict], attributes_by_requirement_id: dict +) -> io.BytesIO: + """ + Create a bar chart showing compliance score by section using ThreatScore formula. + + Args: + requirements_list (list[dict]): List of requirement dictionaries with status and findings data. + attributes_by_requirement_id (dict): Mapping of requirement IDs to their attributes including risk level and weight. + + Returns: + io.BytesIO: A BytesIO buffer containing the chart image in PNG format. + """ + # Define expected sections + expected_sections = [ + "1. IAM", + "2. Attack Surface", + "3. Logging and Monitoring", + "4. Encryption", + ] + + # Initialize all expected sections with default values + sections_data = { + section: { + "numerator": 0, + "denominator": 0, + "has_findings": False, + } + for section in expected_sections + } + + # Collect data from requirements + for requirement in requirements_list: + requirement_id = requirement["id"] + requirement_attributes = attributes_by_requirement_id.get(requirement_id, {}) + + metadata = requirement_attributes.get("attributes", {}).get( + "req_attributes", [] + ) + if metadata: + m = metadata[0] + section = getattr(m, "Section", "Unknown") + + # Add section if not in expected list (for flexibility) + if section not in sections_data: + sections_data[section] = { + "numerator": 0, + "denominator": 0, + "has_findings": False, + } + + # Get findings data + passed_findings = requirement["attributes"].get("passed_findings", 0) + total_findings = requirement["attributes"].get("total_findings", 0) + + if total_findings > 0: + sections_data[section]["has_findings"] = True + risk_level = getattr(m, "LevelOfRisk", 0) + weight = getattr(m, "Weight", 0) + + # Calculate using ThreatScore formula from UI + rate_i = passed_findings / total_findings + rfac_i = 1 + 0.25 * risk_level + + sections_data[section]["numerator"] += ( + rate_i * total_findings * weight * rfac_i + ) + sections_data[section]["denominator"] += ( + total_findings * weight * rfac_i + ) + + section_names = [] + compliance_percentages = [] + + for section, data in sections_data.items(): + if data["has_findings"] and data["denominator"] > 0: + compliance_percentage = (data["numerator"] / data["denominator"]) * 100 + else: + compliance_percentage = 100 # No findings = 100% (PASS) + + section_names.append(section) + compliance_percentages.append(compliance_percentage) + + # Sort alphabetically by section name + sorted_data = sorted( + zip(section_names, compliance_percentages), + key=lambda x: x[0], + ) + section_names, compliance_percentages = ( + zip(*sorted_data) if sorted_data else ([], []) + ) + + fig, ax = plt.subplots(figsize=(12, 8)) + + colors_list = [] + for percentage in compliance_percentages: + if percentage >= 80: + color = "#4CAF50" + elif percentage >= 60: + color = "#8BC34A" + elif percentage >= 40: + color = "#FFEB3B" + elif percentage >= 20: + color = "#FF9800" + else: + color = "#F44336" + colors_list.append(color) + + bars = ax.bar(section_names, compliance_percentages, color=colors_list) + + ax.set_ylabel("Compliance Score (%)", fontsize=12) + ax.set_xlabel("Section", fontsize=12) + ax.set_ylim(0, 100) + + for bar, percentage in zip(bars, compliance_percentages): + height = bar.get_height() + ax.text( + bar.get_x() + bar.get_width() / 2.0, + height + 1, + f"{percentage:.1f}%", + ha="center", + va="bottom", + fontweight="bold", + ) + + plt.xticks(rotation=45, ha="right") + + ax.grid(True, alpha=0.3, axis="y") + + plt.tight_layout() + + buffer = io.BytesIO() + plt.savefig(buffer, format="png", dpi=300, bbox_inches="tight") + buffer.seek(0) + plt.close() + + return buffer + + +def _add_pdf_footer(canvas_obj: canvas.Canvas, doc: SimpleDocTemplate) -> None: + """ + Add footer with page number and branding to each page of the PDF. + + Args: + canvas_obj (canvas.Canvas): The ReportLab canvas object for drawing. + doc (SimpleDocTemplate): The document template containing page information. + """ + width, height = doc.pagesize + page_num_text = f"Page {doc.page}" + canvas_obj.setFont("PlusJakartaSans", 9) + canvas_obj.setFillColorRGB(0.4, 0.4, 0.4) + canvas_obj.drawString(30, 20, page_num_text) + powered_text = "Powered by Prowler" + text_width = canvas_obj.stringWidth(powered_text, "PlusJakartaSans", 9) + canvas_obj.drawString(width - text_width - 30, 20, powered_text) + + +def _aggregate_requirement_statistics_from_database( + tenant_id: str, scan_id: str +) -> dict[str, dict[str, int]]: + """ + Aggregate finding statistics by check_id using database aggregation. + + This function uses Django ORM aggregation to calculate pass/fail statistics + entirely in the database, avoiding the need to load findings into memory. + + Args: + tenant_id (str): The tenant ID for Row-Level Security context. + scan_id (str): The ID of the scan to retrieve findings for. + + Returns: + dict[str, dict[str, int]]: Dictionary mapping check_id to statistics: + - 'passed' (int): Number of passed findings for this check + - 'total' (int): Total number of findings for this check + + Example: + { + 'aws_iam_user_mfa_enabled': {'passed': 10, 'total': 15}, + 'aws_s3_bucket_public_access': {'passed': 0, 'total': 5} + } + """ + requirement_statistics_by_check_id = {} + + with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): + # Use database aggregation to calculate stats without loading findings into memory + aggregated_statistics_queryset = ( + Finding.all_objects.filter(tenant_id=tenant_id, scan_id=scan_id) + .values("check_id") + .annotate( + total_findings=Count("id"), + passed_findings=Count("id", filter=Q(status=StatusChoices.PASS)), + ) + ) + + for aggregated_stat in aggregated_statistics_queryset: + check_id = aggregated_stat["check_id"] + requirement_statistics_by_check_id[check_id] = { + "passed": aggregated_stat["passed_findings"], + "total": aggregated_stat["total_findings"], + } + + logger.info( + f"Aggregated statistics for {len(requirement_statistics_by_check_id)} unique checks" + ) + return requirement_statistics_by_check_id + + +def _load_findings_for_requirement_checks( + tenant_id: str, scan_id: str, check_ids: list[str], prowler_provider +) -> dict[str, list[FindingOutput]]: + """ + Load findings for specific check IDs on-demand. + + This function loads only the findings needed for a specific set of checks, + minimizing memory usage by avoiding loading all findings at once. This is used + when generating detailed findings tables for specific requirements in the PDF. + + Args: + tenant_id (str): The tenant ID for Row-Level Security context. + scan_id (str): The ID of the scan to retrieve findings for. + check_ids (list[str]): List of check IDs to load findings for. + prowler_provider: The initialized Prowler provider instance. + + Returns: + dict[str, list[FindingOutput]]: Dictionary mapping check_id to list of FindingOutput objects. + + Example: + { + 'aws_iam_user_mfa_enabled': [FindingOutput(...), FindingOutput(...)], + 'aws_s3_bucket_public_access': [FindingOutput(...)] + } + """ + findings_by_check_id = defaultdict(list) + + if not check_ids: + return dict(findings_by_check_id) + + logger.info(f"Loading findings for {len(check_ids)} checks on-demand") + + findings_queryset = ( + Finding.all_objects.filter( + tenant_id=tenant_id, scan_id=scan_id, check_id__in=check_ids + ) + .order_by("uid") + .iterator() + ) + + with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): + for batch, is_last_batch in batched( + findings_queryset, DJANGO_FINDINGS_BATCH_SIZE + ): + for finding_model in batch: + finding_output = FindingOutput.transform_api_finding( + finding_model, prowler_provider + ) + findings_by_check_id[finding_output.check_id].append(finding_output) + + total_findings_loaded = sum( + len(findings) for findings in findings_by_check_id.values() + ) + logger.info( + f"Loaded {total_findings_loaded} findings for {len(findings_by_check_id)} checks" + ) + + return dict(findings_by_check_id) + + +def _calculate_requirements_data_from_statistics( + compliance_obj, requirement_statistics_by_check_id: dict[str, dict[str, int]] +) -> tuple[dict[str, dict], list[dict]]: + """ + Calculate requirement status and statistics using pre-aggregated database statistics. + + This function uses O(n) lookups with pre-aggregated statistics from the database, + avoiding the need to iterate over all findings for each requirement. + + Args: + compliance_obj: The compliance framework object containing requirements. + requirement_statistics_by_check_id (dict[str, dict[str, int]]): Pre-aggregated statistics + mapping check_id to {'passed': int, 'total': int} counts. + + Returns: + tuple[dict[str, dict], list[dict]]: A tuple containing: + - attributes_by_requirement_id: Dictionary mapping requirement IDs to their attributes. + - requirements_list: List of requirement dictionaries with status and statistics. + """ + attributes_by_requirement_id = {} + requirements_list = [] + + compliance_framework = getattr(compliance_obj, "Framework", "N/A") + compliance_version = getattr(compliance_obj, "Version", "N/A") + + for requirement in compliance_obj.Requirements: + requirement_id = requirement.Id + requirement_description = getattr(requirement, "Description", "") + requirement_checks = getattr(requirement, "Checks", []) + requirement_attributes = getattr(requirement, "Attributes", []) + + # Store requirement metadata for later use + attributes_by_requirement_id[requirement_id] = { + "attributes": { + "req_attributes": requirement_attributes, + "checks": requirement_checks, + }, + "description": requirement_description, + } + + # Calculate aggregated passed and total findings for this requirement + total_passed_findings = 0 + total_findings_count = 0 + + for check_id in requirement_checks: + if check_id in requirement_statistics_by_check_id: + check_statistics = requirement_statistics_by_check_id[check_id] + total_findings_count += check_statistics["total"] + total_passed_findings += check_statistics["passed"] + + # Determine overall requirement status based on findings + if total_findings_count > 0: + if total_passed_findings == total_findings_count: + requirement_status = StatusChoices.PASS + else: + # Partial pass or complete fail both count as FAIL + requirement_status = StatusChoices.FAIL + else: + # No findings means manual review required + requirement_status = StatusChoices.MANUAL + + requirements_list.append( + { + "id": requirement_id, + "attributes": { + "framework": compliance_framework, + "version": compliance_version, + "status": requirement_status, + "description": requirement_description, + "passed_findings": total_passed_findings, + "total_findings": total_findings_count, + }, + } + ) + + return attributes_by_requirement_id, requirements_list + + +def generate_threatscore_report( + tenant_id: str, + scan_id: str, + compliance_id: str, + output_path: str, + provider_id: str, + only_failed: bool = True, + min_risk_level: int = 4, +) -> None: + """ + Generate a PDF compliance report based on Prowler ThreatScore framework. + + This function creates a comprehensive PDF report containing: + - Compliance overview and metadata + - Section-by-section compliance scores with charts + - Overall ThreatScore calculation + - Critical failed requirements + - Detailed findings for each requirement + + Args: + tenant_id (str): The tenant ID for Row-Level Security context. + scan_id (str): ID of the scan executed by Prowler. + compliance_id (str): ID of the compliance framework (e.g., "prowler_threatscore_aws"). + output_path (str): Output PDF file path (e.g., "/tmp/threatscore_report.pdf"). + provider_id (str): Provider ID for the scan. + only_failed (bool): If True, only requirements with status "FAIL" will be included + in the detailed requirements section. Defaults to True. + min_risk_level (int): Minimum risk level for critical failed requirements. Defaults to 4. + + Raises: + Exception: If any error occurs during PDF generation, it will be logged and re-raised. + """ + logger.info( + f"Generating the report for the scan {scan_id} with provider {provider_id}" + ) + try: + # Get PDF styles + pdf_styles = _create_pdf_styles() + title_style = pdf_styles["title"] + h1 = pdf_styles["h1"] + h2 = pdf_styles["h2"] + h3 = pdf_styles["h3"] + normal = pdf_styles["normal"] + normal_center = pdf_styles["normal_center"] + + # Get compliance and provider information + with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): + provider_obj = Provider.objects.get(id=provider_id) + prowler_provider = initialize_prowler_provider(provider_obj) + provider_type = provider_obj.provider + + frameworks_bulk = Compliance.get_bulk(provider_type) + compliance_obj = frameworks_bulk[compliance_id] + compliance_framework = getattr(compliance_obj, "Framework", "N/A") + compliance_version = getattr(compliance_obj, "Version", "N/A") + compliance_name = getattr(compliance_obj, "Name", "N/A") + compliance_description = getattr(compliance_obj, "Description", "") + + # Aggregate requirement statistics from database (memory-efficient) + logger.info(f"Aggregating requirement statistics for scan {scan_id}") + requirement_statistics_by_check_id = ( + _aggregate_requirement_statistics_from_database(tenant_id, scan_id) + ) + + # Calculate requirements data using aggregated statistics + attributes_by_requirement_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + compliance_obj, requirement_statistics_by_check_id + ) + ) + + # Initialize PDF document + doc = SimpleDocTemplate( + output_path, + pagesize=letter, + title=f"Prowler ThreatScore Report - {compliance_framework}", + author="Prowler", + subject=f"Compliance Report for {compliance_framework}", + creator="Prowler Engineering Team", + keywords=f"compliance,{compliance_framework},security,framework,prowler", + ) + + elements = [] + + # Add logo + img_path = os.path.join( + os.path.dirname(__file__), "../assets/img/prowler_logo.png" + ) + logo = Image( + img_path, + width=5 * inch, + height=1 * inch, + ) + elements.append(logo) + + elements.append(Spacer(1, 0.5 * inch)) + elements.append(Paragraph("Prowler ThreatScore Report", title_style)) + elements.append(Spacer(1, 0.5 * inch)) + + # Add compliance information table + info_data = [ + ["Framework:", compliance_framework], + ["ID:", compliance_id], + ["Name:", Paragraph(compliance_name, normal_center)], + ["Version:", compliance_version], + ["Scan ID:", scan_id], + ["Description:", Paragraph(compliance_description, normal_center)], + ] + info_table = Table(info_data, colWidths=[2 * inch, 4 * inch]) + info_table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (0, 5), colors.Color(0.2, 0.4, 0.6)), + ("TEXTCOLOR", (0, 0), (0, 5), colors.white), + ("FONTNAME", (0, 0), (0, 5), "FiraCode"), + ("BACKGROUND", (1, 0), (1, 5), colors.Color(0.95, 0.97, 1.0)), + ("TEXTCOLOR", (1, 0), (1, 5), colors.Color(0.2, 0.2, 0.2)), + ("FONTNAME", (1, 0), (1, 5), "PlusJakartaSans"), + ("ALIGN", (0, 0), (-1, -1), "LEFT"), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("FONTSIZE", (0, 0), (-1, -1), 11), + ("GRID", (0, 0), (-1, -1), 1, colors.Color(0.7, 0.8, 0.9)), + ("LEFTPADDING", (0, 0), (-1, -1), 10), + ("RIGHTPADDING", (0, 0), (-1, -1), 10), + ("TOPPADDING", (0, 0), (-1, -1), 8), + ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ] + ) + ) + + elements.append(info_table) + elements.append(PageBreak()) + + # Add compliance score chart + elements.append(Paragraph("Compliance Score by Sections", h1)) + elements.append(Spacer(1, 0.2 * inch)) + + chart_buffer = _create_section_score_chart( + requirements_list, attributes_by_requirement_id + ) + chart_image = Image(chart_buffer, width=7 * inch, height=5.5 * inch) + elements.append(chart_image) + + # Calculate overall ThreatScore using the same formula as the UI + numerator = 0 + denominator = 0 + has_findings = False + + for requirement in requirements_list: + requirement_id = requirement["id"] + requirement_attributes = attributes_by_requirement_id.get( + requirement_id, {} + ) + + # Get findings data + passed_findings = requirement["attributes"].get("passed_findings", 0) + total_findings = requirement["attributes"].get("total_findings", 0) + + # Skip if no findings (avoid division by zero) + if total_findings == 0: + continue + + has_findings = True + metadata = requirement_attributes.get("attributes", {}).get( + "req_attributes", [] + ) + if metadata and len(metadata) > 0: + m = metadata[0] + risk_level = getattr(m, "LevelOfRisk", 0) + weight = getattr(m, "Weight", 0) + + # Calculate using ThreatScore formula from UI + rate_i = passed_findings / total_findings + rfac_i = 1 + 0.25 * risk_level + + numerator += rate_i * total_findings * weight * rfac_i + denominator += total_findings * weight * rfac_i + + # Calculate ThreatScore (percentualScore) + # If no findings exist, consider it 100% (PASS) + if not has_findings: + overall_compliance = 100 + elif denominator > 0: + overall_compliance = (numerator / denominator) * 100 + else: + overall_compliance = 0 + + elements.append(Spacer(1, 0.3 * inch)) + + summary_data = [ + ["ThreatScore:", f"{overall_compliance:.2f}%"], + ] + + if overall_compliance >= 80: + compliance_color = colors.Color(0.2, 0.8, 0.2) + elif overall_compliance >= 60: + compliance_color = colors.Color(0.8, 0.8, 0.2) + else: + compliance_color = colors.Color(0.8, 0.2, 0.2) + + summary_table = Table(summary_data, colWidths=[2.5 * inch, 2 * inch]) + summary_table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (0, 0), colors.Color(0.1, 0.3, 0.5)), + ("TEXTCOLOR", (0, 0), (0, 0), colors.white), + ("FONTNAME", (0, 0), (0, 0), "FiraCode"), + ("FONTSIZE", (0, 0), (0, 0), 12), + ("BACKGROUND", (1, 0), (1, 0), compliance_color), + ("TEXTCOLOR", (1, 0), (1, 0), colors.white), + ("FONTNAME", (1, 0), (1, 0), "FiraCode"), + ("FONTSIZE", (1, 0), (1, 0), 16), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("GRID", (0, 0), (-1, -1), 1.5, colors.Color(0.5, 0.6, 0.7)), + ("LEFTPADDING", (0, 0), (-1, -1), 12), + ("RIGHTPADDING", (0, 0), (-1, -1), 12), + ("TOPPADDING", (0, 0), (-1, -1), 10), + ("BOTTOMPADDING", (0, 0), (-1, -1), 10), + ] + ) + ) + + elements.append(summary_table) + elements.append(PageBreak()) + + # Add requirements index + elements.append(Paragraph("Requirements Index", h1)) + + sections = {} + for ( + requirement_id, + requirement_attributes, + ) in attributes_by_requirement_id.items(): + meta = requirement_attributes["attributes"]["req_attributes"][0] + section = getattr(meta, "Section", "N/A") + subsection = getattr(meta, "SubSection", "N/A") + title = getattr(meta, "Title", "N/A") + + if section not in sections: + sections[section] = {} + if subsection not in sections[section]: + sections[section][subsection] = [] + + sections[section][subsection].append({"id": requirement_id, "title": title}) + + section_num = 1 + for section_name, subsections in sections.items(): + elements.append(Paragraph(f"{section_num}. {section_name}", h2)) + + subsection_num = 1 + for subsection_name, requirements in subsections.items(): + elements.append(Paragraph(f"{subsection_name}", h3)) + + req_num = 1 + for req in requirements: + elements.append(Paragraph(f"{req['id']} - {req['title']}", normal)) + req_num += 1 + + subsection_num += 1 + + section_num += 1 + elements.append(Spacer(1, 0.1 * inch)) + + elements.append(PageBreak()) + + # Add critical failed requirements section + elements.append(Paragraph("Top Requirements by Level of Risk", h1)) + elements.append(Spacer(1, 0.1 * inch)) + elements.append( + Paragraph( + f"Critical Failed Requirements (Risk Level ≥ {min_risk_level})", h2 + ) + ) + elements.append(Spacer(1, 0.2 * inch)) + + critical_failed_requirements = [] + for requirement in requirements_list: + requirement_status = requirement["attributes"]["status"] + if requirement_status == StatusChoices.FAIL: + requirement_id = requirement["id"] + metadata = ( + attributes_by_requirement_id.get(requirement_id, {}) + .get("attributes", {}) + .get("req_attributes", [{}])[0] + ) + if metadata: + risk_level = getattr(metadata, "LevelOfRisk", 0) + weight = getattr(metadata, "Weight", 0) + + if risk_level >= min_risk_level: + critical_failed_requirements.append( + { + "requirement": requirement, + "attributes": attributes_by_requirement_id[ + requirement_id + ], + "risk_level": risk_level, + "weight": weight, + "metadata": metadata, + } + ) + + critical_failed_requirements.sort( + key=lambda x: (x["risk_level"], x["weight"]), reverse=True + ) + + if not critical_failed_requirements: + elements.append( + Paragraph( + "✅ No critical failed requirements found. Great job!", normal + ) + ) + else: + elements.append( + Paragraph( + f"Found {len(critical_failed_requirements)} critical failed requirements that require immediate attention:", + normal, + ) + ) + elements.append(Spacer(1, 0.5 * inch)) + + table_data = [["Risk", "Weight", "Requirement ID", "Title", "Section"]] + + for idx, critical_failed_requirement in enumerate( + critical_failed_requirements + ): + requirement_id = critical_failed_requirement["requirement"]["id"] + risk_level = critical_failed_requirement["risk_level"] + weight = critical_failed_requirement["weight"] + title = getattr(critical_failed_requirement["metadata"], "Title", "N/A") + section = getattr( + critical_failed_requirement["metadata"], "Section", "N/A" + ) + + if len(title) > 50: + title = title[:47] + "..." + + table_data.append( + [str(risk_level), str(weight), requirement_id, title, section] + ) + + critical_table = Table( + table_data, + colWidths=[0.7 * inch, 0.9 * inch, 1.3 * inch, 3.1 * inch, 1.5 * inch], + ) + + critical_table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.Color(0.8, 0.2, 0.2)), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "FiraCode"), + ("FONTSIZE", (0, 0), (-1, 0), 10), + ("BACKGROUND", (0, 1), (0, -1), colors.Color(0.8, 0.2, 0.2)), + ("TEXTCOLOR", (0, 1), (0, -1), colors.white), + ("FONTNAME", (0, 1), (0, -1), "FiraCode"), + ("ALIGN", (0, 1), (0, -1), "CENTER"), + ("FONTSIZE", (0, 1), (0, -1), 12), + ("ALIGN", (1, 1), (1, -1), "CENTER"), + ("FONTNAME", (1, 1), (1, -1), "FiraCode"), + ("FONTNAME", (2, 1), (2, -1), "FiraCode"), + ("FONTSIZE", (2, 1), (2, -1), 9), + ("FONTNAME", (3, 1), (-1, -1), "PlusJakartaSans"), + ("FONTSIZE", (3, 1), (-1, -1), 8), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("GRID", (0, 0), (-1, -1), 1, colors.Color(0.7, 0.7, 0.7)), + ("LEFTPADDING", (0, 0), (-1, -1), 6), + ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 8), + ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ( + "BACKGROUND", + (1, 1), + (-1, -1), + colors.Color(0.98, 0.98, 0.98), + ), + ] + ) + ) + + for idx, critical_failed_requirement in enumerate( + critical_failed_requirements + ): + row_idx = idx + 1 + weight = critical_failed_requirement["weight"] + + if weight >= 150: + weight_color = colors.Color(0.8, 0.2, 0.2) + elif weight >= 100: + weight_color = colors.Color(0.9, 0.6, 0.2) + else: + weight_color = colors.Color(0.9, 0.9, 0.2) + + critical_table.setStyle( + TableStyle( + [ + ("BACKGROUND", (1, row_idx), (1, row_idx), weight_color), + ("TEXTCOLOR", (1, row_idx), (1, row_idx), colors.white), + ] + ) + ) + + elements.append(critical_table) + elements.append(Spacer(1, 0.2 * inch)) + + # Get styles for warning + styles = getSampleStyleSheet() + warning_text = """ + IMMEDIATE ACTION REQUIRED:
+ These requirements have the highest risk levels and have failed compliance checks. + Please prioritize addressing these issues to improve your security posture. + """ + + warning_style = ParagraphStyle( + "Warning", + parent=styles["Normal"], + fontSize=11, + textColor=colors.Color(0.8, 0.2, 0.2), + spaceBefore=10, + spaceAfter=10, + leftIndent=20, + rightIndent=20, + fontName="PlusJakartaSans", + backColor=colors.Color(1.0, 0.95, 0.95), + borderWidth=2, + borderColor=colors.Color(0.8, 0.2, 0.2), + borderPadding=10, + ) + + elements.append(Paragraph(warning_text, warning_style)) + + elements.append(PageBreak()) + + # Add detailed requirements section + def get_weight_for_requirement(requirement_dict): + requirement_id = requirement_dict["id"] + requirement_attributes = attributes_by_requirement_id.get( + requirement_id, {} + ) + metadata = requirement_attributes.get("attributes", {}).get( + "req_attributes", [] + ) + if metadata: + return getattr(metadata[0], "Weight", 0) + return 0 + + sorted_requirements = sorted( + requirements_list, key=get_weight_for_requirement, reverse=True + ) + + if only_failed: + sorted_requirements = [ + requirement + for requirement in sorted_requirements + if requirement["attributes"]["status"] == StatusChoices.FAIL + ] + + # Collect all check IDs for requirements that will be displayed + # This allows us to load only the findings we actually need (memory optimization) + check_ids_to_load = [] + for requirement in sorted_requirements: + requirement_id = requirement["id"] + requirement_attributes = attributes_by_requirement_id.get( + requirement_id, {} + ) + check_ids = requirement_attributes.get("attributes", {}).get("checks", []) + check_ids_to_load.extend(check_ids) + + # Load findings on-demand only for the checks that will be displayed + logger.info( + f"Loading findings on-demand for {len(sorted_requirements)} requirements" + ) + findings_by_check_id = _load_findings_for_requirement_checks( + tenant_id, scan_id, check_ids_to_load, prowler_provider + ) + + for requirement in sorted_requirements: + requirement_id = requirement["id"] + requirement_attributes = attributes_by_requirement_id.get( + requirement_id, {} + ) + requirement_description = requirement["attributes"]["description"] + requirement_status = requirement["attributes"]["status"] + + elements.append( + Paragraph( + f"{requirement_id}: {requirement_attributes.get('description', requirement_description)}", + h1, + ) + ) + + status_component = _create_status_component(requirement_status) + elements.append(status_component) + elements.append(Spacer(1, 0.1 * inch)) + + metadata = requirement_attributes.get("attributes", {}).get( + "req_attributes", [] + ) + if metadata and len(metadata) > 0: + m = metadata[0] + elements.append(Paragraph("Title: ", h3)) + elements.append(Paragraph(f"{getattr(m, 'Title', 'N/A')}", normal)) + elements.append(Paragraph("Section: ", h3)) + elements.append(Paragraph(f"{getattr(m, 'Section', 'N/A')}", normal)) + elements.append(Paragraph("SubSection: ", h3)) + elements.append(Paragraph(f"{getattr(m, 'SubSection', 'N/A')}", normal)) + elements.append(Paragraph("Description: ", h3)) + elements.append( + Paragraph(f"{getattr(m, 'AttributeDescription', 'N/A')}", normal) + ) + elements.append(Paragraph("Additional Information: ", h3)) + elements.append( + Paragraph(f"{getattr(m, 'AdditionalInformation', 'N/A')}", normal) + ) + elements.append(Spacer(1, 0.1 * inch)) + + risk_level = getattr(m, "LevelOfRisk", 0) + weight = getattr(m, "Weight", 0) + + if requirement_status == StatusChoices.PASS: + score = risk_level * weight + else: + score = 0 + + risk_component = _create_risk_component(risk_level, weight, score) + elements.append(risk_component) + elements.append(Spacer(1, 0.1 * inch)) + + # Get findings for this requirement's checks (loaded on-demand earlier) + requirement_check_ids = requirement_attributes.get("attributes", {}).get( + "checks", [] + ) + for check_id in requirement_check_ids: + elements.append(Paragraph(f"Check: {check_id}", h2)) + elements.append(Spacer(1, 0.1 * inch)) + + # Get findings for this check (already loaded on-demand) + check_findings = findings_by_check_id.get(check_id, []) + + if not check_findings: + elements.append( + Paragraph("- No information for this finding currently", normal) + ) + else: + findings_table_data = [ + [ + "Finding", + "Resource name", + "Severity", + "Status", + "Region", + ] + ] + for finding_output in check_findings: + check_metadata = getattr(finding_output, "metadata", {}) + finding_title = getattr( + check_metadata, + "CheckTitle", + getattr(finding_output, "check_id", ""), + ) + resource_name = getattr(finding_output, "resource_name", "") + if not resource_name: + resource_name = getattr(finding_output, "resource_uid", "") + severity = getattr(check_metadata, "Severity", "").capitalize() + finding_status = getattr(finding_output, "status", "").upper() + region = getattr(finding_output, "region", "global") + + findings_table_data.append( + [ + Paragraph(finding_title, normal_center), + Paragraph(resource_name, normal_center), + Paragraph(severity, normal_center), + Paragraph(finding_status, normal_center), + Paragraph(region, normal_center), + ] + ) + findings_table = Table( + findings_table_data, + colWidths=[ + 2.5 * inch, + 3 * inch, + 0.9 * inch, + 0.9 * inch, + 0.9 * inch, + ], + ) + findings_table.setStyle( + TableStyle( + [ + ( + "BACKGROUND", + (0, 0), + (-1, 0), + colors.Color(0.2, 0.4, 0.6), + ), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTNAME", (0, 0), (-1, 0), "FiraCode"), + ("ALIGN", (0, 0), (0, 0), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("FONTSIZE", (0, 0), (-1, -1), 9), + ( + "GRID", + (0, 0), + (-1, -1), + 0.1, + colors.Color(0.7, 0.8, 0.9), + ), + ("LEFTPADDING", (0, 0), (0, 0), 0), + ("RIGHTPADDING", (0, 0), (0, 0), 0), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ] + ) + ) + elements.append(findings_table) + elements.append(Spacer(1, 0.1 * inch)) + + elements.append(PageBreak()) + + # Build the PDF + doc.build(elements, onFirstPage=_add_pdf_footer, onLaterPages=_add_pdf_footer) + except Exception as e: + logger.info( + f"Error building the document, line {e.__traceback__.tb_lineno} -- {e}" + ) + raise e + + +def generate_threatscore_report_job( + tenant_id: str, scan_id: str, provider_id: str +) -> dict[str, bool | str]: + """ + Job function to generate a threatscore report and upload it to S3. + + This function orchestrates the complete report generation workflow: + 1. Validates that the scan has findings + 2. Checks provider type compatibility + 3. Generates the output directory + 4. Calls generate_threatscore_report to create the PDF + 5. Uploads the PDF to S3 + 6. Cleans up temporary files + + Args: + tenant_id (str): The tenant ID for Row-Level Security context. + scan_id (str): The ID of the scan to generate a report for. + provider_id (str): The ID of the provider used in the scan. + + Returns: + dict[str, bool | str]: A dictionary containing: + - 'upload' (bool): True if the report was successfully uploaded to S3, False otherwise. + - 'error' (str): Error message if an exception occurred (only present on error). + """ + # Check if the scan has findings and get provider info + with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): + if not ScanSummary.objects.filter(scan_id=scan_id).exists(): + logger.info(f"No findings found for scan {scan_id}") + return {"upload": False} + + provider_obj = Provider.objects.get(id=provider_id) + provider_uid = provider_obj.uid + provider_type = provider_obj.provider + + if provider_type not in ["aws", "azure", "gcp", "m365"]: + logger.info( + f"Provider {provider_id} is not supported for threatscore report" + ) + return {"upload": False} + + # This compliance is hardcoded because is the only one that is available for the threatscore report + compliance_id = f"prowler_threatscore_{provider_type}" + logger.info( + f"Generating threatscore report for scan {scan_id} with compliance {compliance_id} inside the job" + ) + try: + logger.info("Generating the output directory") + out_dir, _, threatscore_path = _generate_output_directory( + DJANGO_TMP_OUTPUT_DIRECTORY, provider_uid, tenant_id, scan_id + ) + except Exception as e: + logger.error(f"Error generating output directory: {e}") + return {"error": str(e)} + + pdf_path = f"{threatscore_path}_threatscore_report.pdf" + logger.info(f"The path for the threatscore report is {pdf_path}") + generate_threatscore_report( + tenant_id=tenant_id, + scan_id=scan_id, + compliance_id=compliance_id, + output_path=pdf_path, + provider_id=provider_id, + only_failed=True, + min_risk_level=4, + ) + + upload_uri = _upload_to_s3(tenant_id, pdf_path, scan_id) + if upload_uri: + try: + rmtree(Path(pdf_path).parent, ignore_errors=True) + except Exception as e: + logger.error(f"Error deleting output files: {e}") + final_location, did_upload = upload_uri, True + else: + final_location, did_upload = out_dir, False + + logger.info(f"Threatscore report outputs at {final_location}") + + return {"upload": did_upload} diff --git a/api/src/backend/tasks/tasks.py b/api/src/backend/tasks/tasks.py index fd4874eb6c..48e7e43dae 100644 --- a/api/src/backend/tasks/tasks.py +++ b/api/src/backend/tasks/tasks.py @@ -26,6 +26,7 @@ from tasks.jobs.integrations import ( upload_s3_integration, upload_security_hub_integration, ) +from tasks.jobs.report import generate_threatscore_report_job from tasks.jobs.scan import ( aggregate_findings, create_compliance_requirements, @@ -64,10 +65,15 @@ def _perform_scan_complete_tasks(tenant_id: str, scan_id: str, provider_id: str) generate_outputs_task.si( scan_id=scan_id, provider_id=provider_id, tenant_id=tenant_id ), - check_integrations_task.si( - tenant_id=tenant_id, - provider_id=provider_id, - scan_id=scan_id, + group( + generate_threatscore_report_task.si( + tenant_id=tenant_id, scan_id=scan_id, provider_id=provider_id + ), + check_integrations_task.si( + tenant_id=tenant_id, + provider_id=provider_id, + scan_id=scan_id, + ), ), ).apply_async() @@ -304,7 +310,7 @@ def generate_outputs_task(scan_id: str, provider_id: str, tenant_id: str): frameworks_bulk = Compliance.get_bulk(provider_type) frameworks_avail = get_compliance_frameworks(provider_type) - out_dir, comp_dir = _generate_output_directory( + out_dir, comp_dir, _ = _generate_output_directory( DJANGO_TMP_OUTPUT_DIRECTORY, provider_uid, tenant_id, scan_id ) @@ -617,3 +623,21 @@ def jira_integration_task( return send_findings_to_jira( tenant_id, integration_id, project_key, issue_type, finding_ids ) + + +@shared_task( + base=RLSTask, + name="scan-threatscore-report", + queue="scan-reports", +) +def generate_threatscore_report_task(tenant_id: str, scan_id: str, provider_id: str): + """ + Task to generate a threatscore report for a given scan. + Args: + tenant_id (str): The tenant identifier. + scan_id (str): The scan identifier. + provider_id (str): The provider identifier. + """ + return generate_threatscore_report_job( + tenant_id=tenant_id, scan_id=scan_id, provider_id=provider_id + ) diff --git a/api/src/backend/tasks/tests/test_export.py b/api/src/backend/tasks/tests/test_export.py index c10f20774b..e91985dbdb 100644 --- a/api/src/backend/tasks/tests/test_export.py +++ b/api/src/backend/tasks/tests/test_export.py @@ -150,15 +150,17 @@ class TestOutputs: provider = "aws" expected_timestamp = "20230615103045" - path, compliance = _generate_output_directory( + path, compliance, threatscore = _generate_output_directory( base_dir, provider, tenant_id, scan_id ) assert os.path.isdir(os.path.dirname(path)) assert os.path.isdir(os.path.dirname(compliance)) + assert os.path.isdir(os.path.dirname(threatscore)) assert path.endswith(f"{provider}-{expected_timestamp}") assert compliance.endswith(f"{provider}-{expected_timestamp}") + assert threatscore.endswith(f"{provider}-{expected_timestamp}") @patch("tasks.jobs.export.rls_transaction") @patch("tasks.jobs.export.Scan") @@ -181,12 +183,14 @@ class TestOutputs: provider = "aws/test@check" expected_timestamp = "20230615103045" - path, compliance = _generate_output_directory( + path, compliance, threatscore = _generate_output_directory( base_dir, provider, tenant_id, scan_id ) assert os.path.isdir(os.path.dirname(path)) assert os.path.isdir(os.path.dirname(compliance)) + assert os.path.isdir(os.path.dirname(threatscore)) assert path.endswith(f"aws-test-check-{expected_timestamp}") assert compliance.endswith(f"aws-test-check-{expected_timestamp}") + assert threatscore.endswith(f"aws-test-check-{expected_timestamp}") diff --git a/api/src/backend/tasks/tests/test_report.py b/api/src/backend/tasks/tests/test_report.py new file mode 100644 index 0000000000..ddc11d3a32 --- /dev/null +++ b/api/src/backend/tasks/tests/test_report.py @@ -0,0 +1,957 @@ +import uuid +from pathlib import Path +from unittest.mock import MagicMock, patch + +import matplotlib +import pytest +from tasks.jobs.report import ( + _aggregate_requirement_statistics_from_database, + _calculate_requirements_data_from_statistics, + _load_findings_for_requirement_checks, + generate_threatscore_report, + generate_threatscore_report_job, +) +from tasks.tasks import generate_threatscore_report_task + +from api.models import Finding, StatusChoices +from prowler.lib.check.models import Severity + +matplotlib.use("Agg") # Use non-interactive backend for tests + + +@pytest.mark.django_db +class TestGenerateThreatscoreReport: + def setup_method(self): + self.scan_id = str(uuid.uuid4()) + self.provider_id = str(uuid.uuid4()) + self.tenant_id = str(uuid.uuid4()) + + def test_no_findings_returns_early(self): + with patch("tasks.jobs.report.ScanSummary.objects.filter") as mock_filter: + mock_filter.return_value.exists.return_value = False + + result = generate_threatscore_report_job( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + assert result == {"upload": False} + mock_filter.assert_called_once_with(scan_id=self.scan_id) + + @patch("tasks.jobs.report.rmtree") + @patch("tasks.jobs.report._upload_to_s3") + @patch("tasks.jobs.report.generate_threatscore_report") + @patch("tasks.jobs.report._generate_output_directory") + @patch("tasks.jobs.report.Provider.objects.get") + @patch("tasks.jobs.report.ScanSummary.objects.filter") + def test_generate_threatscore_report_happy_path( + self, + mock_scan_summary_filter, + mock_provider_get, + mock_generate_output_directory, + mock_generate_report, + mock_upload, + mock_rmtree, + ): + mock_scan_summary_filter.return_value.exists.return_value = True + + mock_provider = MagicMock() + mock_provider.uid = "provider-uid" + mock_provider.provider = "aws" + mock_provider_get.return_value = mock_provider + + mock_generate_output_directory.return_value = ( + "/tmp/output", + "/tmp/compressed", + "/tmp/threatscore_path", + ) + + mock_upload.return_value = "s3://bucket/threatscore_report.pdf" + + result = generate_threatscore_report_job( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + assert result == {"upload": True} + mock_generate_report.assert_called_once_with( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + compliance_id="prowler_threatscore_aws", + output_path="/tmp/threatscore_path_threatscore_report.pdf", + provider_id=self.provider_id, + only_failed=True, + min_risk_level=4, + ) + mock_rmtree.assert_called_once_with( + Path("/tmp/threatscore_path_threatscore_report.pdf").parent, + ignore_errors=True, + ) + + def test_generate_threatscore_report_fails_upload(self): + with ( + patch("tasks.jobs.report.ScanSummary.objects.filter") as mock_filter, + patch("tasks.jobs.report.Provider.objects.get") as mock_provider_get, + patch("tasks.jobs.report._generate_output_directory") as mock_gen_dir, + patch("tasks.jobs.report.generate_threatscore_report"), + patch("tasks.jobs.report._upload_to_s3", return_value=None), + ): + mock_filter.return_value.exists.return_value = True + + # Mock provider + mock_provider = MagicMock() + mock_provider.uid = "aws-provider-uid" + mock_provider.provider = "aws" + mock_provider_get.return_value = mock_provider + + mock_gen_dir.return_value = ( + "/tmp/output", + "/tmp/compressed", + "/tmp/threatscore_path", + ) + + result = generate_threatscore_report_job( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + assert result == {"upload": False} + + def test_generate_threatscore_report_logs_rmtree_exception(self, caplog): + with ( + patch("tasks.jobs.report.ScanSummary.objects.filter") as mock_filter, + patch("tasks.jobs.report.Provider.objects.get") as mock_provider_get, + patch("tasks.jobs.report._generate_output_directory") as mock_gen_dir, + patch("tasks.jobs.report.generate_threatscore_report"), + patch( + "tasks.jobs.report._upload_to_s3", return_value="s3://bucket/report.pdf" + ), + patch( + "tasks.jobs.report.rmtree", side_effect=Exception("Test deletion error") + ), + ): + mock_filter.return_value.exists.return_value = True + + # Mock provider + mock_provider = MagicMock() + mock_provider.uid = "aws-provider-uid" + mock_provider.provider = "aws" + mock_provider_get.return_value = mock_provider + + mock_gen_dir.return_value = ( + "/tmp/output", + "/tmp/compressed", + "/tmp/threatscore_path", + ) + + with caplog.at_level("ERROR"): + generate_threatscore_report_job( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + assert "Error deleting output files" in caplog.text + + def test_generate_threatscore_report_azure_provider(self): + with ( + patch("tasks.jobs.report.ScanSummary.objects.filter") as mock_filter, + patch("tasks.jobs.report.Provider.objects.get") as mock_provider_get, + patch("tasks.jobs.report._generate_output_directory") as mock_gen_dir, + patch("tasks.jobs.report.generate_threatscore_report") as mock_generate, + patch( + "tasks.jobs.report._upload_to_s3", return_value="s3://bucket/report.pdf" + ), + patch("tasks.jobs.report.rmtree"), + ): + mock_filter.return_value.exists.return_value = True + + mock_provider = MagicMock() + mock_provider.uid = "azure-provider-uid" + mock_provider.provider = "azure" + mock_provider_get.return_value = mock_provider + + mock_gen_dir.return_value = ( + "/tmp/output", + "/tmp/compressed", + "/tmp/threatscore_path", + ) + + generate_threatscore_report_job( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + mock_generate.assert_called_once_with( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + compliance_id="prowler_threatscore_azure", + output_path="/tmp/threatscore_path_threatscore_report.pdf", + provider_id=self.provider_id, + only_failed=True, + min_risk_level=4, + ) + + +@pytest.mark.django_db +class TestAggregateRequirementStatistics: + """Test suite for _aggregate_requirement_statistics_from_database function.""" + + def test_aggregates_findings_correctly(self, tenants_fixture, scans_fixture): + """Verify correct pass/total counts per check are aggregated from database.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + # Create findings with different check_ids and statuses + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-1", + check_id="check_1", + status=StatusChoices.PASS, + severity=Severity.high, + impact=Severity.high, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-2", + check_id="check_1", + status=StatusChoices.FAIL, + severity=Severity.high, + impact=Severity.high, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-3", + check_id="check_2", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + result = _aggregate_requirement_statistics_from_database( + str(tenant.id), str(scan.id) + ) + + assert result == { + "check_1": {"passed": 1, "total": 2}, + "check_2": {"passed": 1, "total": 1}, + } + + def test_handles_empty_scan(self, tenants_fixture, scans_fixture): + """Return empty dict when no findings exist for the scan.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + result = _aggregate_requirement_statistics_from_database( + str(tenant.id), str(scan.id) + ) + + assert result == {} + + def test_multiple_findings_same_check(self, tenants_fixture, scans_fixture): + """Aggregate multiple findings for same check_id correctly.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + # Create 5 findings for same check, 3 passed + for i in range(3): + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid=f"finding-pass-{i}", + check_id="check_same", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + for i in range(2): + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid=f"finding-fail-{i}", + check_id="check_same", + status=StatusChoices.FAIL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + result = _aggregate_requirement_statistics_from_database( + str(tenant.id), str(scan.id) + ) + + assert result == {"check_same": {"passed": 3, "total": 5}} + + def test_only_failed_findings(self, tenants_fixture, scans_fixture): + """Correctly count when all findings are FAIL status.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-fail-1", + check_id="check_fail", + status=StatusChoices.FAIL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-fail-2", + check_id="check_fail", + status=StatusChoices.FAIL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + result = _aggregate_requirement_statistics_from_database( + str(tenant.id), str(scan.id) + ) + + assert result == {"check_fail": {"passed": 0, "total": 2}} + + def test_mixed_statuses(self, tenants_fixture, scans_fixture): + """Test with PASS, FAIL, and MANUAL statuses mixed.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-pass", + check_id="check_mixed", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-fail", + check_id="check_mixed", + status=StatusChoices.FAIL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-manual", + check_id="check_mixed", + status=StatusChoices.MANUAL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + result = _aggregate_requirement_statistics_from_database( + str(tenant.id), str(scan.id) + ) + + # Only PASS status is counted as passed + assert result == {"check_mixed": {"passed": 1, "total": 3}} + + +@pytest.mark.django_db +class TestLoadFindingsForChecks: + """Test suite for _load_findings_for_requirement_checks function.""" + + def test_loads_only_requested_checks( + self, tenants_fixture, scans_fixture, providers_fixture + ): + """Verify only findings for specified check_ids are loaded.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + providers_fixture[0] + + # Create findings with different check_ids + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-1", + check_id="check_requested", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-2", + check_id="check_not_requested", + status=StatusChoices.FAIL, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + mock_provider = MagicMock() + + with patch( + "tasks.jobs.report.FindingOutput.transform_api_finding" + ) as mock_transform: + mock_finding_output = MagicMock() + mock_finding_output.check_id = "check_requested" + mock_transform.return_value = mock_finding_output + + result = _load_findings_for_requirement_checks( + str(tenant.id), str(scan.id), ["check_requested"], mock_provider + ) + + # Only one finding should be loaded + assert "check_requested" in result + assert "check_not_requested" not in result + assert len(result["check_requested"]) == 1 + assert mock_transform.call_count == 1 + + def test_empty_check_ids_returns_empty( + self, tenants_fixture, scans_fixture, providers_fixture + ): + """Return empty dict when check_ids list is empty.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + mock_provider = MagicMock() + + result = _load_findings_for_requirement_checks( + str(tenant.id), str(scan.id), [], mock_provider + ) + + assert result == {} + + def test_groups_by_check_id( + self, tenants_fixture, scans_fixture, providers_fixture + ): + """Multiple findings for same check are grouped correctly.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + # Create multiple findings for same check + for i in range(3): + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid=f"finding-{i}", + check_id="check_group", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + mock_provider = MagicMock() + + with patch( + "tasks.jobs.report.FindingOutput.transform_api_finding" + ) as mock_transform: + mock_finding_output = MagicMock() + mock_finding_output.check_id = "check_group" + mock_transform.return_value = mock_finding_output + + result = _load_findings_for_requirement_checks( + str(tenant.id), str(scan.id), ["check_group"], mock_provider + ) + + assert len(result["check_group"]) == 3 + + def test_transforms_to_finding_output( + self, tenants_fixture, scans_fixture, providers_fixture + ): + """Findings are transformed using FindingOutput.transform_api_finding.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid="finding-transform", + check_id="check_transform", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + mock_provider = MagicMock() + + with patch( + "tasks.jobs.report.FindingOutput.transform_api_finding" + ) as mock_transform: + mock_finding_output = MagicMock() + mock_finding_output.check_id = "check_transform" + mock_transform.return_value = mock_finding_output + + result = _load_findings_for_requirement_checks( + str(tenant.id), str(scan.id), ["check_transform"], mock_provider + ) + + # Verify transform was called + mock_transform.assert_called_once() + # Verify the transformed output is in the result + assert result["check_transform"][0] == mock_finding_output + + def test_batched_iteration(self, tenants_fixture, scans_fixture, providers_fixture): + """Works correctly with multiple batches of findings.""" + tenant = tenants_fixture[0] + scan = scans_fixture[0] + + # Create enough findings to ensure batching (assuming batch size > 1) + for i in range(10): + Finding.objects.create( + tenant_id=tenant.id, + scan=scan, + uid=f"finding-batch-{i}", + check_id="check_batch", + status=StatusChoices.PASS, + severity=Severity.medium, + impact=Severity.medium, + check_metadata={}, + raw_result={}, + ) + + mock_provider = MagicMock() + + with patch( + "tasks.jobs.report.FindingOutput.transform_api_finding" + ) as mock_transform: + mock_finding_output = MagicMock() + mock_finding_output.check_id = "check_batch" + mock_transform.return_value = mock_finding_output + + result = _load_findings_for_requirement_checks( + str(tenant.id), str(scan.id), ["check_batch"], mock_provider + ) + + # All 10 findings should be loaded regardless of batching + assert len(result["check_batch"]) == 10 + assert mock_transform.call_count == 10 + + +@pytest.mark.django_db +class TestCalculateRequirementsData: + """Test suite for _calculate_requirements_data_from_statistics function.""" + + def test_requirement_status_all_pass(self): + """Status is PASS when all findings for requirement checks pass.""" + mock_compliance = MagicMock() + mock_compliance.Framework = "TestFramework" + mock_compliance.Version = "1.0" + + mock_requirement = MagicMock() + mock_requirement.Id = "req_1" + mock_requirement.Description = "Test requirement" + mock_requirement.Checks = ["check_1", "check_2"] + mock_requirement.Attributes = [MagicMock()] + + mock_compliance.Requirements = [mock_requirement] + + requirement_statistics = { + "check_1": {"passed": 5, "total": 5}, + "check_2": {"passed": 3, "total": 3}, + } + + attributes_by_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + mock_compliance, requirement_statistics + ) + ) + + assert len(requirements_list) == 1 + assert requirements_list[0]["attributes"]["status"] == StatusChoices.PASS + assert requirements_list[0]["attributes"]["passed_findings"] == 8 + assert requirements_list[0]["attributes"]["total_findings"] == 8 + + def test_requirement_status_some_fail(self): + """Status is FAIL when some findings fail.""" + mock_compliance = MagicMock() + mock_compliance.Framework = "TestFramework" + mock_compliance.Version = "1.0" + + mock_requirement = MagicMock() + mock_requirement.Id = "req_2" + mock_requirement.Description = "Test requirement with failures" + mock_requirement.Checks = ["check_3"] + mock_requirement.Attributes = [MagicMock()] + + mock_compliance.Requirements = [mock_requirement] + + requirement_statistics = { + "check_3": {"passed": 2, "total": 5}, + } + + attributes_by_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + mock_compliance, requirement_statistics + ) + ) + + assert len(requirements_list) == 1 + assert requirements_list[0]["attributes"]["status"] == StatusChoices.FAIL + assert requirements_list[0]["attributes"]["passed_findings"] == 2 + assert requirements_list[0]["attributes"]["total_findings"] == 5 + + def test_requirement_status_no_findings(self): + """Status is MANUAL when no findings exist for requirement.""" + mock_compliance = MagicMock() + mock_compliance.Framework = "TestFramework" + mock_compliance.Version = "1.0" + + mock_requirement = MagicMock() + mock_requirement.Id = "req_3" + mock_requirement.Description = "Manual requirement" + mock_requirement.Checks = ["check_nonexistent"] + mock_requirement.Attributes = [MagicMock()] + + mock_compliance.Requirements = [mock_requirement] + + requirement_statistics = {} + + attributes_by_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + mock_compliance, requirement_statistics + ) + ) + + assert len(requirements_list) == 1 + assert requirements_list[0]["attributes"]["status"] == StatusChoices.MANUAL + assert requirements_list[0]["attributes"]["passed_findings"] == 0 + assert requirements_list[0]["attributes"]["total_findings"] == 0 + + def test_aggregates_multiple_checks(self): + """Correctly sum stats across multiple checks in requirement.""" + mock_compliance = MagicMock() + mock_compliance.Framework = "TestFramework" + mock_compliance.Version = "1.0" + + mock_requirement = MagicMock() + mock_requirement.Id = "req_4" + mock_requirement.Description = "Multi-check requirement" + mock_requirement.Checks = ["check_a", "check_b", "check_c"] + mock_requirement.Attributes = [MagicMock()] + + mock_compliance.Requirements = [mock_requirement] + + requirement_statistics = { + "check_a": {"passed": 10, "total": 15}, + "check_b": {"passed": 5, "total": 10}, + "check_c": {"passed": 0, "total": 5}, + } + + attributes_by_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + mock_compliance, requirement_statistics + ) + ) + + assert len(requirements_list) == 1 + # 10 + 5 + 0 = 15 passed + assert requirements_list[0]["attributes"]["passed_findings"] == 15 + # 15 + 10 + 5 = 30 total + assert requirements_list[0]["attributes"]["total_findings"] == 30 + # Not all passed, so should be FAIL + assert requirements_list[0]["attributes"]["status"] == StatusChoices.FAIL + + def test_returns_correct_structure(self): + """Verify tuple structure and dict keys are correct.""" + mock_compliance = MagicMock() + mock_compliance.Framework = "TestFramework" + mock_compliance.Version = "1.0" + + mock_attribute = MagicMock() + mock_requirement = MagicMock() + mock_requirement.Id = "req_5" + mock_requirement.Description = "Structure test" + mock_requirement.Checks = ["check_struct"] + mock_requirement.Attributes = [mock_attribute] + + mock_compliance.Requirements = [mock_requirement] + + requirement_statistics = {"check_struct": {"passed": 1, "total": 1}} + + attributes_by_id, requirements_list = ( + _calculate_requirements_data_from_statistics( + mock_compliance, requirement_statistics + ) + ) + + # Verify attributes_by_id structure + assert "req_5" in attributes_by_id + assert "attributes" in attributes_by_id["req_5"] + assert "description" in attributes_by_id["req_5"] + assert "req_attributes" in attributes_by_id["req_5"]["attributes"] + assert "checks" in attributes_by_id["req_5"]["attributes"] + + # Verify requirements_list structure + assert len(requirements_list) == 1 + req = requirements_list[0] + assert "id" in req + assert "attributes" in req + assert "framework" in req["attributes"] + assert "version" in req["attributes"] + assert "status" in req["attributes"] + assert "description" in req["attributes"] + assert "passed_findings" in req["attributes"] + assert "total_findings" in req["attributes"] + + +@pytest.mark.django_db +class TestGenerateThreatscoreReportFunction: + def setup_method(self): + self.scan_id = str(uuid.uuid4()) + self.provider_id = str(uuid.uuid4()) + self.tenant_id = str(uuid.uuid4()) + self.compliance_id = "prowler_threatscore_aws" + self.output_path = "/tmp/test_threatscore_report.pdf" + + @patch("tasks.jobs.report.initialize_prowler_provider") + @patch("tasks.jobs.report.Provider.objects.get") + @patch("tasks.jobs.report.Compliance.get_bulk") + @patch("tasks.jobs.report._aggregate_requirement_statistics_from_database") + @patch("tasks.jobs.report._calculate_requirements_data_from_statistics") + @patch("tasks.jobs.report._load_findings_for_requirement_checks") + @patch("tasks.jobs.report.SimpleDocTemplate") + @patch("tasks.jobs.report.Image") + @patch("tasks.jobs.report.Spacer") + @patch("tasks.jobs.report.Paragraph") + @patch("tasks.jobs.report.PageBreak") + @patch("tasks.jobs.report.Table") + @patch("tasks.jobs.report.TableStyle") + @patch("tasks.jobs.report.plt.subplots") + @patch("tasks.jobs.report.plt.savefig") + @patch("tasks.jobs.report.io.BytesIO") + def test_generate_threatscore_report_success( + self, + mock_bytesio, + mock_savefig, + mock_subplots, + mock_table_style, + mock_table, + mock_page_break, + mock_paragraph, + mock_spacer, + mock_image, + mock_doc_template, + mock_load_findings, + mock_calculate_requirements, + mock_aggregate_statistics, + mock_compliance_get_bulk, + mock_provider_get, + mock_initialize_provider, + ): + """Test the updated generate_threatscore_report using new memory-efficient architecture.""" + mock_provider = MagicMock() + mock_provider.provider = "aws" + mock_provider_get.return_value = mock_provider + + prowler_provider = MagicMock() + mock_initialize_provider.return_value = prowler_provider + + # Mock compliance object with requirements + mock_compliance_obj = MagicMock() + mock_compliance_obj.Framework = "ProwlerThreatScore" + mock_compliance_obj.Version = "1.0" + mock_compliance_obj.Description = "Test Description" + + # Configure requirement with properly set numeric attributes for chart generation + mock_requirement = MagicMock() + mock_requirement.Id = "req_1" + mock_requirement.Description = "Test requirement" + mock_requirement.Checks = ["check_1"] + + # Create a properly configured attribute mock with numeric values + mock_requirement_attr = MagicMock() + mock_requirement_attr.Section = "1. IAM" + mock_requirement_attr.SubSection = "1.1 Identity" + mock_requirement_attr.Title = "Test Requirement Title" + mock_requirement_attr.LevelOfRisk = 3 + mock_requirement_attr.Weight = 100 + mock_requirement_attr.AttributeDescription = "Test requirement description" + mock_requirement_attr.AdditionalInformation = "Additional test information" + + mock_requirement.Attributes = [mock_requirement_attr] + mock_compliance_obj.Requirements = [mock_requirement] + + mock_compliance_get_bulk.return_value = { + self.compliance_id: mock_compliance_obj + } + + # Mock the aggregated statistics from database + mock_aggregate_statistics.return_value = {"check_1": {"passed": 5, "total": 10}} + + # Mock the calculated requirements data with properly configured attributes + mock_attributes_by_id = { + "req_1": { + "attributes": { + "req_attributes": [mock_requirement_attr], + "checks": ["check_1"], + }, + "description": "Test requirement", + } + } + mock_requirements_list = [ + { + "id": "req_1", + "attributes": { + "framework": "ProwlerThreatScore", + "version": "1.0", + "status": StatusChoices.FAIL, + "description": "Test requirement", + "passed_findings": 5, + "total_findings": 10, + }, + } + ] + mock_calculate_requirements.return_value = ( + mock_attributes_by_id, + mock_requirements_list, + ) + + # Mock the on-demand loaded findings + mock_finding_output = MagicMock() + mock_finding_output.check_id = "check_1" + mock_finding_output.status = "FAIL" + mock_finding_output.metadata = MagicMock() + mock_finding_output.metadata.CheckTitle = "Test Check" + mock_finding_output.metadata.Severity = "HIGH" + mock_finding_output.resource_name = "test-resource" + mock_finding_output.region = "us-east-1" + + mock_load_findings.return_value = {"check_1": [mock_finding_output]} + + # Mock PDF generation components + mock_doc = MagicMock() + mock_doc_template.return_value = mock_doc + + mock_fig, mock_ax = MagicMock(), MagicMock() + mock_subplots.return_value = (mock_fig, mock_ax) + mock_buffer = MagicMock() + mock_bytesio.return_value = mock_buffer + + mock_image.return_value = MagicMock() + mock_spacer.return_value = MagicMock() + mock_paragraph.return_value = MagicMock() + mock_page_break.return_value = MagicMock() + mock_table.return_value = MagicMock() + mock_table_style.return_value = MagicMock() + + # Execute the function + generate_threatscore_report( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + compliance_id=self.compliance_id, + output_path=self.output_path, + provider_id=self.provider_id, + only_failed=True, + min_risk_level=4, + ) + + # Verify the new workflow was followed + mock_provider_get.assert_called_once_with(id=self.provider_id) + mock_initialize_provider.assert_called_once_with(mock_provider) + mock_compliance_get_bulk.assert_called_once_with("aws") + + # Verify the new functions were called in correct order with correct parameters + mock_aggregate_statistics.assert_called_once_with(self.tenant_id, self.scan_id) + mock_calculate_requirements.assert_called_once_with( + mock_compliance_obj, {"check_1": {"passed": 5, "total": 10}} + ) + mock_load_findings.assert_called_once_with( + self.tenant_id, self.scan_id, ["check_1"], prowler_provider + ) + + # Verify PDF was built + mock_doc_template.assert_called_once() + mock_doc.build.assert_called_once() + + @patch("tasks.jobs.report.initialize_prowler_provider") + @patch("tasks.jobs.report.Provider.objects.get") + @patch("tasks.jobs.report.Compliance.get_bulk") + @patch("tasks.jobs.report.Finding.all_objects.filter") + def test_generate_threatscore_report_exception_handling( + self, + mock_finding_filter, + mock_compliance_get_bulk, + mock_provider_get, + mock_initialize_provider, + ): + mock_provider_get.side_effect = Exception("Provider not found") + + with pytest.raises(Exception, match="Provider not found"): + generate_threatscore_report( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + compliance_id=self.compliance_id, + output_path=self.output_path, + provider_id=self.provider_id, + only_failed=True, + min_risk_level=4, + ) + + +@pytest.mark.django_db +class TestGenerateThreatscoreReportTask: + def setup_method(self): + self.scan_id = str(uuid.uuid4()) + self.provider_id = str(uuid.uuid4()) + self.tenant_id = str(uuid.uuid4()) + + @patch("tasks.tasks.generate_threatscore_report_job") + def test_generate_threatscore_report_task_calls_job(self, mock_generate_job): + mock_generate_job.return_value = {"upload": True} + + result = generate_threatscore_report_task( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + assert result == {"upload": True} + mock_generate_job.assert_called_once_with( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) + + @patch("tasks.tasks.generate_threatscore_report_job") + def test_generate_threatscore_report_task_handles_job_exception( + self, mock_generate_job + ): + mock_generate_job.side_effect = Exception("Job failed") + + with pytest.raises(Exception, match="Job failed"): + generate_threatscore_report_task( + tenant_id=self.tenant_id, + scan_id=self.scan_id, + provider_id=self.provider_id, + ) diff --git a/api/src/backend/tasks/tests/test_tasks.py b/api/src/backend/tasks/tests/test_tasks.py index b4262027f6..a98341ef35 100644 --- a/api/src/backend/tasks/tests/test_tasks.py +++ b/api/src/backend/tasks/tests/test_tasks.py @@ -98,7 +98,11 @@ class TestGenerateOutputs: ), patch( "tasks.tasks._generate_output_directory", - return_value=("out-dir", "comp-dir"), + return_value=( + "/tmp/test/out-dir", + "/tmp/test/comp-dir", + "/tmp/test/threat-dir", + ), ), patch("tasks.tasks.Scan.all_objects.filter") as mock_scan_update, patch("tasks.tasks.rmtree"), @@ -126,7 +130,8 @@ class TestGenerateOutputs: patch("tasks.tasks.get_compliance_frameworks"), patch("tasks.tasks.Finding.all_objects.filter") as mock_findings, patch( - "tasks.tasks._generate_output_directory", return_value=("out", "comp") + "tasks.tasks._generate_output_directory", + return_value=("/tmp/test/out", "/tmp/test/comp", "/tmp/test/threat"), ), patch("tasks.tasks.FindingOutput._transform_findings_stats"), patch("tasks.tasks.FindingOutput.transform_api_finding"), @@ -168,15 +173,35 @@ class TestGenerateOutputs: mock_finding_output = MagicMock() mock_finding_output.compliance = {"cis": ["requirement-1", "requirement-2"]} + html_writer_mock = MagicMock() + html_writer_mock._data = [] + html_writer_mock.close_file = False + html_writer_mock.transform = MagicMock() + html_writer_mock.batch_write_data_to_file = MagicMock() + + compliance_writer_mock = MagicMock() + compliance_writer_mock._data = [] + compliance_writer_mock.close_file = False + compliance_writer_mock.transform = MagicMock() + compliance_writer_mock.batch_write_data_to_file = MagicMock() + + # Create a mock class that returns our mock instance when called + mock_compliance_class = MagicMock(return_value=compliance_writer_mock) + + mock_provider = MagicMock() + mock_provider.provider = "aws" + mock_provider.uid = "test-provider-uid" + with ( patch("tasks.tasks.ScanSummary.objects.filter") as mock_filter, - patch("tasks.tasks.Provider.objects.get"), + patch("tasks.tasks.Provider.objects.get", return_value=mock_provider), patch("tasks.tasks.initialize_prowler_provider"), patch("tasks.tasks.Compliance.get_bulk", return_value={"cis": MagicMock()}), patch("tasks.tasks.get_compliance_frameworks", return_value=["cis"]), patch("tasks.tasks.Finding.all_objects.filter") as mock_findings, patch( - "tasks.tasks._generate_output_directory", return_value=("out", "comp") + "tasks.tasks._generate_output_directory", + return_value=("/tmp/test/out", "/tmp/test/comp", "/tmp/test/threat"), ), patch( "tasks.tasks.FindingOutput._transform_findings_stats", @@ -190,6 +215,20 @@ class TestGenerateOutputs: patch("tasks.tasks._upload_to_s3", return_value="s3://bucket/f.zip"), patch("tasks.tasks.Scan.all_objects.filter"), patch("tasks.tasks.rmtree"), + patch( + "tasks.tasks.OUTPUT_FORMATS_MAPPING", + { + "html": { + "class": lambda *args, **kwargs: html_writer_mock, + "suffix": ".html", + "kwargs": {}, + } + }, + ), + patch( + "tasks.tasks.COMPLIANCE_CLASS_MAP", + {"aws": [(lambda x: True, mock_compliance_class)]}, + ), ): mock_filter.return_value.exists.return_value = True mock_findings.return_value.order_by.return_value.iterator.return_value = [ @@ -197,29 +236,12 @@ class TestGenerateOutputs: True, ] - html_writer_mock = MagicMock() - with ( - patch( - "tasks.tasks.OUTPUT_FORMATS_MAPPING", - { - "html": { - "class": lambda *args, **kwargs: html_writer_mock, - "suffix": ".html", - "kwargs": {}, - } - }, - ), - patch( - "tasks.tasks.COMPLIANCE_CLASS_MAP", - {"aws": [(lambda x: True, MagicMock())]}, - ), - ): - generate_outputs_task( - scan_id=self.scan_id, - provider_id=self.provider_id, - tenant_id=self.tenant_id, - ) - html_writer_mock.batch_write_data_to_file.assert_called_once() + generate_outputs_task( + scan_id=self.scan_id, + provider_id=self.provider_id, + tenant_id=self.tenant_id, + ) + html_writer_mock.batch_write_data_to_file.assert_called_once() def test_transform_called_only_on_second_batch(self): raw1 = MagicMock() @@ -256,7 +278,11 @@ class TestGenerateOutputs: ), patch( "tasks.tasks._generate_output_directory", - return_value=("outdir", "compdir"), + return_value=( + "/tmp/test/outdir", + "/tmp/test/compdir", + "/tmp/test/threatdir", + ), ), patch("tasks.tasks._compress_output_files", return_value="outdir.zip"), patch("tasks.tasks._upload_to_s3", return_value="s3://bucket/outdir.zip"), @@ -303,12 +329,14 @@ class TestGenerateOutputs: def __init__(self, *args, **kwargs): self.transform_calls = [] self._data = [] + self.close_file = False writer_instances.append(self) def transform(self, fos, comp_obj, name): self.transform_calls.append((fos, comp_obj, name)) def batch_write_data_to_file(self): + # Mock implementation - do nothing pass two_batches = [ @@ -329,7 +357,11 @@ class TestGenerateOutputs: patch("tasks.tasks.get_compliance_frameworks", return_value=["cis"]), patch( "tasks.tasks._generate_output_directory", - return_value=("outdir", "compdir"), + return_value=( + "/tmp/test/outdir", + "/tmp/test/compdir", + "/tmp/test/threatdir", + ), ), patch("tasks.tasks.FindingOutput._transform_findings_stats"), patch( @@ -368,15 +400,35 @@ class TestGenerateOutputs: mock_finding_output = MagicMock() mock_finding_output.compliance = {"cis": ["requirement-1", "requirement-2"]} + json_writer_mock = MagicMock() + json_writer_mock._data = [] + json_writer_mock.close_file = False + json_writer_mock.transform = MagicMock() + json_writer_mock.batch_write_data_to_file = MagicMock() + + compliance_writer_mock = MagicMock() + compliance_writer_mock._data = [] + compliance_writer_mock.close_file = False + compliance_writer_mock.transform = MagicMock() + compliance_writer_mock.batch_write_data_to_file = MagicMock() + + # Create a mock class that returns our mock instance when called + mock_compliance_class = MagicMock(return_value=compliance_writer_mock) + + mock_provider = MagicMock() + mock_provider.provider = "aws" + mock_provider.uid = "test-provider-uid" + with ( patch("tasks.tasks.ScanSummary.objects.filter") as mock_filter, - patch("tasks.tasks.Provider.objects.get"), + patch("tasks.tasks.Provider.objects.get", return_value=mock_provider), patch("tasks.tasks.initialize_prowler_provider"), patch("tasks.tasks.Compliance.get_bulk", return_value={"cis": MagicMock()}), patch("tasks.tasks.get_compliance_frameworks", return_value=["cis"]), patch("tasks.tasks.Finding.all_objects.filter") as mock_findings, patch( - "tasks.tasks._generate_output_directory", return_value=("out", "comp") + "tasks.tasks._generate_output_directory", + return_value=("/tmp/test/out", "/tmp/test/comp", "/tmp/test/threat"), ), patch( "tasks.tasks.FindingOutput._transform_findings_stats", @@ -390,6 +442,20 @@ class TestGenerateOutputs: patch("tasks.tasks._upload_to_s3", return_value="s3://bucket/file.zip"), patch("tasks.tasks.Scan.all_objects.filter"), patch("tasks.tasks.rmtree", side_effect=Exception("Test deletion error")), + patch( + "tasks.tasks.OUTPUT_FORMATS_MAPPING", + { + "json": { + "class": lambda *args, **kwargs: json_writer_mock, + "suffix": ".json", + "kwargs": {}, + } + }, + ), + patch( + "tasks.tasks.COMPLIANCE_CLASS_MAP", + {"aws": [(lambda x: True, mock_compliance_class)]}, + ), ): mock_filter.return_value.exists.return_value = True mock_findings.return_value.order_by.return_value.iterator.return_value = [ @@ -397,29 +463,13 @@ class TestGenerateOutputs: True, ] - with ( - patch( - "tasks.tasks.OUTPUT_FORMATS_MAPPING", - { - "json": { - "class": lambda *args, **kwargs: MagicMock(), - "suffix": ".json", - "kwargs": {}, - } - }, - ), - patch( - "tasks.tasks.COMPLIANCE_CLASS_MAP", - {"aws": [(lambda x: True, MagicMock())]}, - ), - ): - with caplog.at_level("ERROR"): - generate_outputs_task( - scan_id=self.scan_id, - provider_id=self.provider_id, - tenant_id=self.tenant_id, - ) - assert "Error deleting output files" in caplog.text + with caplog.at_level("ERROR"): + generate_outputs_task( + scan_id=self.scan_id, + provider_id=self.provider_id, + tenant_id=self.tenant_id, + ) + assert "Error deleting output files" in caplog.text @patch("tasks.tasks.rls_transaction") @patch("tasks.tasks.Integration.objects.filter") @@ -435,7 +485,8 @@ class TestGenerateOutputs: patch("tasks.tasks.get_compliance_frameworks", return_value=[]), patch("tasks.tasks.Finding.all_objects.filter") as mock_findings, patch( - "tasks.tasks._generate_output_directory", return_value=("out", "comp") + "tasks.tasks._generate_output_directory", + return_value=("/tmp/test/out", "/tmp/test/comp", "/tmp/test/threat"), ), patch("tasks.tasks.FindingOutput._transform_findings_stats"), patch("tasks.tasks.FindingOutput.transform_api_finding"), @@ -476,8 +527,15 @@ class TestScanCompleteTasks: @patch("tasks.tasks.create_compliance_requirements_task.apply_async") @patch("tasks.tasks.perform_scan_summary_task.si") @patch("tasks.tasks.generate_outputs_task.si") + @patch("tasks.tasks.generate_threatscore_report_task.si") + @patch("tasks.tasks.check_integrations_task.si") def test_scan_complete_tasks( - self, mock_outputs_task, mock_scan_summary_task, mock_compliance_tasks + self, + mock_check_integrations_task, + mock_threatscore_task, + mock_outputs_task, + mock_scan_summary_task, + mock_compliance_tasks, ): _perform_scan_complete_tasks("tenant-id", "scan-id", "provider-id") mock_compliance_tasks.assert_called_once_with( @@ -492,6 +550,16 @@ class TestScanCompleteTasks: provider_id="provider-id", tenant_id="tenant-id", ) + mock_threatscore_task.assert_called_once_with( + tenant_id="tenant-id", + scan_id="scan-id", + provider_id="provider-id", + ) + mock_check_integrations_task.assert_called_once_with( + tenant_id="tenant-id", + provider_id="provider-id", + scan_id="scan-id", + ) @pytest.mark.django_db @@ -662,7 +730,7 @@ class TestCheckIntegrationsTask: mock_initialize_provider.return_value = MagicMock() mock_compliance_bulk.return_value = {} mock_get_frameworks.return_value = [] - mock_generate_dir.return_value = ("out-dir", "comp-dir") + mock_generate_dir.return_value = ("out-dir", "comp-dir", "threat-dir") mock_transform_stats.return_value = {"stats": "data"} # Mock findings @@ -787,7 +855,7 @@ class TestCheckIntegrationsTask: mock_initialize_provider.return_value = MagicMock() mock_compliance_bulk.return_value = {} mock_get_frameworks.return_value = [] - mock_generate_dir.return_value = ("out-dir", "comp-dir") + mock_generate_dir.return_value = ("out-dir", "comp-dir", "threat-dir") mock_transform_stats.return_value = {"stats": "data"} # Mock findings @@ -903,7 +971,7 @@ class TestCheckIntegrationsTask: mock_initialize_provider.return_value = MagicMock() mock_compliance_bulk.return_value = {} mock_get_frameworks.return_value = [] - mock_generate_dir.return_value = ("out-dir", "comp-dir") + mock_generate_dir.return_value = ("out-dir", "comp-dir", "threat-dir") mock_transform_stats.return_value = {"stats": "data"} # Mock findings diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 9f0174f4ba..7c5a3edc09 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler UI** are documented in this file. - React Compiler support for automatic optimization [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748) - Turbopack support for faster development builds [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748) - Add compliance name in compliance detail view [(#8775)](https://github.com/prowler-cloud/prowler/pull/8775) +- PDF reporting for Prowler ThreatScore [(#8867)](https://github.com/prowler-cloud/prowler/pull/8867) - Support C5 compliance framework for the AWS provider [(#8830)](https://github.com/prowler-cloud/prowler/pull/8830) - API key management in user profile [(#8308)](https://github.com/prowler-cloud/prowler/pull/8308) - Refresh access token error handling [(#8864)](https://github.com/prowler-cloud/prowler/pull/8864) diff --git a/ui/actions/scans/scans.ts b/ui/actions/scans/scans.ts index c17f056f3f..6dc3654344 100644 --- a/ui/actions/scans/scans.ts +++ b/ui/actions/scans/scans.ts @@ -268,3 +268,45 @@ export const getComplianceCsv = async ( }; } }; + +export const getThreatScorePdf = async (scanId: string) => { + const headers = await getAuthHeaders({ contentType: false }); + + const url = new URL(`${apiBaseUrl}/scans/${scanId}/threatscore`); + + try { + const response = await fetch(url.toString(), { headers }); + + if (response.status === 202) { + const json = await response.json(); + const taskId = json?.data?.id; + const state = json?.data?.attributes?.state; + return { + pending: true, + state, + taskId, + }; + } + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + errorData?.errors?.detail || + "Unable to retrieve ThreatScore PDF report. Contact support if the issue continues.", + ); + } + + const arrayBuffer = await response.arrayBuffer(); + const base64 = Buffer.from(arrayBuffer).toString("base64"); + + return { + success: true, + data: base64, + filename: `scan-${scanId}-threatscore.pdf`, + }; + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx index 2ea1bc823b..2b7cfd8cfd 100644 --- a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx +++ b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx @@ -29,6 +29,8 @@ import { } from "@/types/compliance"; import { ScanEntity } from "@/types/scans"; +import { ThreatScoreDownloadButton } from "./threatscore-download-button"; + interface ComplianceDetailSearchParams { complianceId: string; version?: string; @@ -143,13 +145,24 @@ export default async function ComplianceDetail({
)} - +
+
+ +
+ {attributesData?.data?.[0]?.attributes?.framework === + "ProwlerThreatScore" && + selectedScanId && ( +
+ +
+ )} +
{ + const [isDownloading, setIsDownloading] = useState(false); + + const handleDownload = async () => { + setIsDownloading(true); + try { + await downloadThreatScorePdf(scanId, toast); + } finally { + setIsDownloading(false); + } + }; + + return ( + + ); +}; diff --git a/ui/app/(prowler)/compliance/page.tsx b/ui/app/(prowler)/compliance/page.tsx index de94cf0fe2..a4bde8104f 100644 --- a/ui/app/(prowler)/compliance/page.tsx +++ b/ui/app/(prowler)/compliance/page.tsx @@ -1,16 +1,22 @@ export const dynamic = "force-dynamic"; import { Suspense } from "react"; -import { getCompliancesOverview } from "@/actions/compliances"; -import { getComplianceOverviewMetadataInfo } from "@/actions/compliances"; +import { + getComplianceAttributes, + getComplianceOverviewMetadataInfo, + getComplianceRequirements, + getCompliancesOverview, +} from "@/actions/compliances"; import { getScans } from "@/actions/scans"; import { ComplianceCard, ComplianceSkeletonGrid, NoScansAvailable, + ThreatScoreBadge, } from "@/components/compliance"; import { ComplianceHeader } from "@/components/compliance/compliance-header/compliance-header"; import { ContentLayout } from "@/components/ui"; +import { calculateThreatScore } from "@/lib/compliance/threatscore-calculator"; import { ExpandedScanData, ScanEntity, @@ -74,6 +80,7 @@ export default async function Compliance({ }) .filter(Boolean) as ExpandedScanData[]; + // Use scanId from URL, or select the first scan if not provided const selectedScanId = resolvedSearchParams.scanId || expandedScansData[0]?.id || null; const query = (filters["filter[search]"] as string) || ""; @@ -94,6 +101,7 @@ export default async function Compliance({ } : undefined; + // Fetch metadata if we have a selected scan const metadataInfoData = selectedScanId ? await getComplianceOverviewMetadataInfo({ query, @@ -105,14 +113,52 @@ export default async function Compliance({ const uniqueRegions = metadataInfoData?.data?.attributes?.regions || []; + // Fetch ThreatScore data if we have a selected scan + let threatScoreData = null; + if ( + selectedScanId && + typeof selectedScanId === "string" && + selectedScan?.providerInfo?.provider + ) { + const complianceId = `prowler_threatscore_${selectedScan.providerInfo.provider.toLowerCase()}`; + + const [attributesData, requirementsData] = await Promise.all([ + getComplianceAttributes(complianceId), + getComplianceRequirements({ + complianceId, + scanId: selectedScanId, + }), + ]); + + threatScoreData = calculateThreatScore(attributesData, requirementsData); + } + return ( {selectedScanId ? ( <> - +
+
+
+ +
+ {threatScoreData && + typeof selectedScanId === "string" && + selectedScan && ( +
+ +
+ )} +
+
}> - {compliancesData.data.map((compliance: ComplianceOverviewData) => { - const { attributes, id } = compliance; - const { framework, version, requirements_passed, total_requirements } = - attributes; + {compliancesData.data + .filter((compliance: ComplianceOverviewData) => { + // Filter out ProwlerThreatScore from the grid + return compliance.attributes.framework !== "ProwlerThreatScore"; + }) + .map((compliance: ComplianceOverviewData) => { + const { attributes, id } = compliance; + const { + framework, + version, + requirements_passed, + total_requirements, + } = attributes; - return ( - - ); - })} + return ( + + ); + })}
); }; diff --git a/ui/components/compliance/compliance-header/data-compliance.tsx b/ui/components/compliance/compliance-header/data-compliance.tsx index 7d29ebfe7d..992dc3d683 100644 --- a/ui/components/compliance/compliance-header/data-compliance.tsx +++ b/ui/components/compliance/compliance-header/data-compliance.tsx @@ -19,11 +19,13 @@ export const DataCompliance = ({ scans }: DataComplianceProps) => { const selectedScanId = scanIdParam || (scans.length > 0 ? scans[0].id : ""); + // Don't auto-push scanId to URL - the server already handles the default scan selection + // This avoids duplicate API calls caused by client-side navigation useEffect(() => { if (!scanIdParam && scans.length > 0) { const params = new URLSearchParams(searchParams); params.set("scanId", scans[0].id); - router.push(`?${params.toString()}`); + router.replace(`?${params.toString()}`, { scroll: false }); } }, [scans, scanIdParam, searchParams, router]); diff --git a/ui/components/compliance/index.ts b/ui/components/compliance/index.ts index 096951a261..4365f4ec65 100644 --- a/ui/components/compliance/index.ts +++ b/ui/components/compliance/index.ts @@ -19,3 +19,5 @@ export * from "./skeletons/compliance-accordion-skeleton"; export * from "./skeletons/compliance-grid-skeleton"; export * from "./skeletons/heatmap-chart-skeleton"; export * from "./skeletons/pie-chart-skeleton"; +export * from "./threatscore-badge"; +export * from "./threatscore-logo"; diff --git a/ui/components/compliance/threatscore-badge.tsx b/ui/components/compliance/threatscore-badge.tsx new file mode 100644 index 0000000000..b9ba9d5b7b --- /dev/null +++ b/ui/components/compliance/threatscore-badge.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { Button } from "@heroui/button"; +import { Card, CardBody } from "@heroui/card"; +import { Progress } from "@heroui/progress"; +import { DownloadIcon, FileTextIcon } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; + +import { ThreatScoreLogo } from "@/components/compliance/threatscore-logo"; +import { toast } from "@/components/ui"; +import { downloadComplianceCsv, downloadThreatScorePdf } from "@/lib/helper"; +import type { ScanEntity } from "@/types/scans"; + +interface ThreatScoreBadgeProps { + score: number; + scanId: string; + provider: string; + selectedScan?: ScanEntity; +} + +export const ThreatScoreBadge = ({ + score, + scanId, + provider, + selectedScan, +}: ThreatScoreBadgeProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [isDownloadingPdf, setIsDownloadingPdf] = useState(false); + const [isDownloadingCsv, setIsDownloadingCsv] = useState(false); + + const complianceId = `prowler_threatscore_${provider.toLowerCase()}`; + + const getScoreColor = (): "success" | "warning" | "danger" => { + if (score >= 80) return "success"; + if (score >= 40) return "warning"; + return "danger"; + }; + + const getTextColor = () => { + if (score >= 80) return "text-success"; + if (score >= 40) return "text-warning"; + return "text-danger"; + }; + + const handleCardClick = () => { + const title = "ProwlerThreatScore"; + const version = "1.0"; + const formattedTitleForUrl = encodeURIComponent(title); + const path = `/compliance/${formattedTitleForUrl}`; + const params = new URLSearchParams(); + + params.set("complianceId", complianceId); + params.set("version", version); + params.set("scanId", scanId); + + if (selectedScan) { + params.set( + "scanData", + JSON.stringify({ + id: selectedScan.id, + providerInfo: selectedScan.providerInfo, + attributes: selectedScan.attributes, + }), + ); + } + + const regionFilter = searchParams.get("filter[region__in]"); + if (regionFilter) { + params.set("filter[region__in]", regionFilter); + } + + router.push(`${path}?${params.toString()}`); + }; + + const handleDownloadPdf = async () => { + setIsDownloadingPdf(true); + try { + await downloadThreatScorePdf(scanId, toast); + } finally { + setIsDownloadingPdf(false); + } + }; + + const handleDownloadCsv = async () => { + setIsDownloadingCsv(true); + try { + await downloadComplianceCsv(scanId, complianceId, toast); + } finally { + setIsDownloadingCsv(false); + } + }; + + return ( + + + +
+ + +
+
+
+ ); +}; diff --git a/ui/components/compliance/threatscore-logo.tsx b/ui/components/compliance/threatscore-logo.tsx new file mode 100644 index 0000000000..d4b98d4ccc --- /dev/null +++ b/ui/components/compliance/threatscore-logo.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; + +export const ThreatScoreLogo = () => { + const { resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + // Avoid hydration mismatch by only rendering after mount + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return
; + } + + const prowlerColor = resolvedTheme === "dark" ? "#fff" : "#000"; + + return ( + + {/* Prowler logo from the new SVG - scaled and positioned to match THREATSCORE size */} + + + + + {/* THREATSCORE text */} + + THREATSCORE + + + {/* Gauge icon - semicircular meter - 1.5x larger */} + + {/* Gauge arcs - drawing from left to right (orange, red, green) */} + + + + + {/* Checkmark */} + + + + ); +}; diff --git a/ui/lib/compliance/threatscore-calculator.ts b/ui/lib/compliance/threatscore-calculator.ts new file mode 100644 index 0000000000..d2067d08fb --- /dev/null +++ b/ui/lib/compliance/threatscore-calculator.ts @@ -0,0 +1,69 @@ +import { AttributesData, RequirementsData } from "@/types/compliance"; + +export interface ThreatScoreResult { + score: number; +} + +/** + * Calculates the ThreatScore for a given provider's compliance data. + * This function replicates the calculation logic from the server-side getThreatScore + * but operates on already-fetched attribute and requirement data. + * + * @param attributesData - Compliance attributes containing metadata like Weight and LevelOfRisk + * @param requirementsData - Compliance requirements containing passed and total findings + * @returns The calculated ThreatScore or null if calculation fails + */ +export function calculateThreatScore( + attributesData: AttributesData | undefined, + requirementsData: RequirementsData | undefined, +): ThreatScoreResult | null { + if (!attributesData?.data || !requirementsData?.data) { + return null; + } + + // Create requirements map for fast lookup + const requirementsMap = new Map(); + for (const req of requirementsData.data) { + requirementsMap.set(req.id, req); + } + + // Calculate ThreatScore using the same formula as the server-side version + let numerator = 0; + let denominator = 0; + let hasFindings = false; + + for (const attributeItem of attributesData.data) { + const id = attributeItem.id; + const metadataArray = attributeItem.attributes?.attributes + ?.metadata as any[]; + const attrs = metadataArray?.[0]; + if (!attrs) continue; + + const requirementData = requirementsMap.get(id); + if (!requirementData) continue; + + const pass_i = requirementData.attributes.passed_findings || 0; + const total_i = requirementData.attributes.total_findings || 0; + + if (total_i === 0) continue; + + hasFindings = true; + const rate_i = pass_i / total_i; + const weight_i = attrs.Weight || 1; + const levelOfRisk = attrs.LevelOfRisk || 0; + const rfac_i = 1 + 0.25 * levelOfRisk; + + numerator += rate_i * total_i * weight_i * rfac_i; + denominator += total_i * weight_i * rfac_i; + } + + const score = !hasFindings + ? 100 + : denominator > 0 + ? (numerator / denominator) * 100 + : 0; + + return { + score: Math.round(score * 100) / 100, + }; +} diff --git a/ui/lib/helper.ts b/ui/lib/helper.ts index 1888b63dcb..89b352c90b 100644 --- a/ui/lib/helper.ts +++ b/ui/lib/helper.ts @@ -1,4 +1,8 @@ -import { getComplianceCsv, getExportsZip } from "@/actions/scans"; +import { + getComplianceCsv, + getExportsZip, + getThreatScorePdf, +} from "@/actions/scans"; import { getTask } from "@/actions/task"; import { auth } from "@/auth.config"; import { useToast } from "@/components/ui"; @@ -137,13 +141,15 @@ export const downloadScanZip = async ( } }; -export const downloadComplianceCsv = async ( - scanId: string, - complianceId: string, +/** + * Generic function to download a file from base64 data + */ +const downloadFile = async ( + result: any, + outputType: string, + successMessage: string, toast: ReturnType["toast"], ): Promise => { - const result = await getComplianceCsv(scanId, complianceId); - if (result?.pending) { toast({ title: "The report is still being generated", @@ -160,7 +166,7 @@ export const downloadComplianceCsv = async ( bytes[i] = binaryString.charCodeAt(i); } - const blob = new Blob([bytes], { type: "text/csv" }); + const blob = new Blob([bytes], { type: outputType }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; @@ -172,7 +178,7 @@ export const downloadComplianceCsv = async ( toast({ title: "Download Complete", - description: "The compliance report has been downloaded successfully.", + description: successMessage, }); } catch (error) { toast({ @@ -201,6 +207,33 @@ export const downloadComplianceCsv = async ( }); }; +export const downloadComplianceCsv = async ( + scanId: string, + complianceId: string, + toast: ReturnType["toast"], +): Promise => { + const result = await getComplianceCsv(scanId, complianceId); + await downloadFile( + result, + "text/csv", + "The compliance report has been downloaded successfully.", + toast, + ); +}; + +export const downloadThreatScorePdf = async ( + scanId: string, + toast: ReturnType["toast"], +): Promise => { + const result = await getThreatScorePdf(scanId); + await downloadFile( + result, + "application/pdf", + "The ThreatScore PDF report has been downloaded successfully.", + toast, + ); +}; + export const isGoogleOAuthEnabled = !!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_ID && !!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_SECRET; From 665662939157074b422ac63ec08147e0a356792f Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:07:28 +0200 Subject: [PATCH 38/57] docs: include docker platform warning in App installation too (#8979) --- docs/getting-started/installation/prowler-app.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/getting-started/installation/prowler-app.mdx b/docs/getting-started/installation/prowler-app.mdx index 862d76a73f..4c49b8d4f0 100644 --- a/docs/getting-started/installation/prowler-app.mdx +++ b/docs/getting-started/installation/prowler-app.mdx @@ -25,6 +25,9 @@ Prowler configuration is based in `.env` files. Every version of Prowler can hav curl -LO https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/.env docker compose up -d ``` + + > Containers are built for `linux/amd64`. If your workstation's architecture is different, please set `DOCKER_DEFAULT_PLATFORM=linux/amd64` in your environment or use the `--platform linux/amd64` flag in the docker command. + _Requirements_: From f8c8dee2b32cf7768f18787f9763a497dc04f710 Mon Sep 17 00:00:00 2001 From: lydiavilchez <114735608+lydiavilchez@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:45:26 +0200 Subject: [PATCH 39/57] feat(gcp): add `cloudstorage_bucket_lifecycle_management_enabled` check (#8936) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 1 + .../__init__.py | 0 ...lifecycle_management_enabled.metadata.json | 34 +++ ...age_bucket_lifecycle_management_enabled.py | 48 ++++ .../cloudstorage/cloudstorage_service.py | 10 + ...ucket_lifecycle_management_enabled_test.py | 223 ++++++++++++++++++ 6 files changed, 316 insertions(+) create mode 100644 prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/__init__.py create mode 100644 prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.metadata.json create mode 100644 prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.py create mode 100644 tests/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 712c53ac25..2b5bdfd42a 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Oracle Cloud provider with CIS 3.0 benchmark [(#8893)](https://github.com/prowler-cloud/prowler/pull/8893) - Support for Atlassian Document Format (ADF) in Jira integration [(#8878)](https://github.com/prowler-cloud/prowler/pull/8878) - Add Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) +- `cloudstorage_bucket_lifecycle_management_enabled` check for GCP provider [(#8936)](https://github.com/prowler-cloud/prowler/pull/8936) ### Changed diff --git a/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/__init__.py b/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.metadata.json b/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.metadata.json new file mode 100644 index 0000000000..450f2d96c5 --- /dev/null +++ b/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "gcp", + "CheckID": "cloudstorage_bucket_lifecycle_management_enabled", + "CheckTitle": "Cloud Storage buckets have lifecycle management enabled", + "CheckType": [], + "ServiceName": "cloudstorage", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "storage.googleapis.com/Bucket", + "Description": "**Google Cloud Storage buckets** are evaluated for the presence of **lifecycle management** with at least one valid rule (supported action and non-empty condition) to automatically transition or delete objects and optimize storage costs.", + "Risk": "Buckets without lifecycle rules can accumulate stale data, increase storage costs, and fail to meet data retention and internal compliance requirements.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudStorage/enable-lifecycle-management.html", + "https://cloud.google.com/storage/docs/lifecycle" + ], + "Remediation": { + "Code": { + "CLI": "gcloud storage buckets update gs:// --lifecycle-file=", + "NativeIaC": "", + "Other": "1) Open Google Cloud Console → Storage → Buckets → \n2) Tab 'Lifecycle'\n3) Add rule(s) to delete or transition objects (e.g., delete after 365 days; transition STANDARD→NEARLINE after 90 days)\n4) Save", + "Terraform": "```hcl\n# Example: enable lifecycle to transition and delete objects\nresource \"google_storage_bucket\" \"example\" {\n name = var.bucket_name\n location = var.location\n\n # Transition STANDARD → NEARLINE after 90 days\n lifecycle_rule {\n action {\n type = \"SetStorageClass\"\n storage_class = \"NEARLINE\"\n }\n condition {\n age = 90\n matches_storage_class = [\"STANDARD\"]\n }\n }\n\n # Delete objects after 365 days\n lifecycle_rule {\n action {\n type = \"Delete\"\n }\n condition {\n age = 365\n }\n }\n}\n```" + }, + "Recommendation": { + "Text": "Configure lifecycle rules to automatically delete stale objects or transition them to colder storage classes according to your organization's retention and cost-optimization policy.", + "Url": "https://hub.prowler.com/check/cloudstorage_bucket_lifecycle_management_enabled" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.py b/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.py new file mode 100644 index 0000000000..951bf57d4f --- /dev/null +++ b/prowler/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, Check_Report_GCP +from prowler.providers.gcp.services.cloudstorage.cloudstorage_client import ( + cloudstorage_client, +) + + +class cloudstorage_bucket_lifecycle_management_enabled(Check): + """Ensure Cloud Storage buckets have lifecycle management enabled with at least one valid rule. + + Reports PASS if a bucket has at least one valid lifecycle rule + (with a supported action and condition), otherwise FAIL. + + """ + + def execute(self) -> list[Check_Report_GCP]: + """Run the lifecycle management check for each Cloud Storage bucket. + + Returns: + list[Check_Report_GCP]: Results for all evaluated buckets. + """ + + findings = [] + for bucket in cloudstorage_client.buckets: + report = Check_Report_GCP(metadata=self.metadata(), resource=bucket) + report.status = "FAIL" + report.status_extended = ( + f"Bucket {bucket.name} does not have lifecycle management enabled." + ) + + rules = bucket.lifecycle_rules + + if rules: + valid_rules = [] + for rule in rules: + action_type = rule.get("action", {}).get("type") + condition = rule.get("condition") + if action_type and condition: + valid_rules.append(rule) + + if valid_rules: + report.status = "PASS" + report.status_extended = f"Bucket {bucket.name} has lifecycle management enabled with {len(valid_rules)} valid rule(s)." + else: + report.status = "FAIL" + report.status_extended = f"Bucket {bucket.name} has lifecycle rules configured but none are valid." + + findings.append(report) + return findings diff --git a/prowler/providers/gcp/services/cloudstorage/cloudstorage_service.py b/prowler/providers/gcp/services/cloudstorage/cloudstorage_service.py index 294455077c..7d155d254f 100644 --- a/prowler/providers/gcp/services/cloudstorage/cloudstorage_service.py +++ b/prowler/providers/gcp/services/cloudstorage/cloudstorage_service.py @@ -31,6 +31,14 @@ class CloudStorage(GCPService): bucket_iam ) or "allUsers" in str(bucket_iam): public = True + + lifecycle_rules = None + lifecycle = bucket.get("lifecycle") + if isinstance(lifecycle, dict): + rules = lifecycle.get("rule") + if isinstance(rules, list): + lifecycle_rules = rules + self.buckets.append( Bucket( name=bucket["name"], @@ -42,6 +50,7 @@ class CloudStorage(GCPService): public=public, retention_policy=bucket.get("retentionPolicy"), project_id=project_id, + lifecycle_rules=lifecycle_rules, ) ) @@ -62,3 +71,4 @@ class Bucket(BaseModel): public: bool project_id: str retention_policy: Optional[dict] = None + lifecycle_rules: Optional[list[dict]] = None diff --git a/tests/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled_test.py b/tests/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled_test.py new file mode 100644 index 0000000000..17016645a7 --- /dev/null +++ b/tests/providers/gcp/services/cloudstorage/cloudstorage_bucket_lifecycle_management_enabled/cloudstorage_bucket_lifecycle_management_enabled_test.py @@ -0,0 +1,223 @@ +from unittest import mock + +from tests.providers.gcp.gcp_fixtures import ( + GCP_PROJECT_ID, + GCP_US_CENTER1_LOCATION, + set_mocked_gcp_provider, +) + + +class TestCloudStorageBucketLifecycleManagementEnabled: + def test_bucket_without_lifecycle_rules(self): + cloudstorage_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_gcp_provider(), + ), + mock.patch( + "prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_client", + new=cloudstorage_client, + ), + ): + from prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled import ( + cloudstorage_bucket_lifecycle_management_enabled, + ) + from prowler.providers.gcp.services.cloudstorage.cloudstorage_service import ( + Bucket, + ) + + cloudstorage_client.project_ids = [GCP_PROJECT_ID] + cloudstorage_client.region = GCP_US_CENTER1_LOCATION + + cloudstorage_client.buckets = [ + Bucket( + name="no-lifecycle", + id="no-lifecycle", + region=GCP_US_CENTER1_LOCATION, + uniform_bucket_level_access=True, + public=False, + retention_policy=None, + project_id=GCP_PROJECT_ID, + lifecycle_rules=[], + ) + ] + + check = cloudstorage_bucket_lifecycle_management_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Bucket {cloudstorage_client.buckets[0].name} does not have lifecycle management enabled." + ) + assert result[0].resource_id == "no-lifecycle" + assert result[0].resource_name == "no-lifecycle" + assert result[0].location == GCP_US_CENTER1_LOCATION + assert result[0].project_id == GCP_PROJECT_ID + + def test_bucket_with_minimal_delete_rule(self): + cloudstorage_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_gcp_provider(), + ), + mock.patch( + "prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_client", + new=cloudstorage_client, + ), + ): + from prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled import ( + cloudstorage_bucket_lifecycle_management_enabled, + ) + from prowler.providers.gcp.services.cloudstorage.cloudstorage_service import ( + Bucket, + ) + + cloudstorage_client.project_ids = [GCP_PROJECT_ID] + cloudstorage_client.region = GCP_US_CENTER1_LOCATION + + cloudstorage_client.buckets = [ + Bucket( + name="delete-rule", + id="delete-rule", + region=GCP_US_CENTER1_LOCATION, + uniform_bucket_level_access=True, + public=False, + retention_policy=None, + project_id=GCP_PROJECT_ID, + lifecycle_rules=[ + {"action": {"type": "Delete"}, "condition": {"age": 30}} + ], + ) + ] + + check = cloudstorage_bucket_lifecycle_management_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Bucket {cloudstorage_client.buckets[0].name} has lifecycle management enabled with 1 valid rule(s)." + ) + assert result[0].resource_id == "delete-rule" + assert result[0].resource_name == "delete-rule" + assert result[0].location == GCP_US_CENTER1_LOCATION + assert result[0].project_id == GCP_PROJECT_ID + + def test_bucket_with_transition_and_delete_rules(self): + cloudstorage_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_gcp_provider(), + ), + mock.patch( + "prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_client", + new=cloudstorage_client, + ), + ): + from prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled import ( + cloudstorage_bucket_lifecycle_management_enabled, + ) + from prowler.providers.gcp.services.cloudstorage.cloudstorage_service import ( + Bucket, + ) + + cloudstorage_client.project_ids = [GCP_PROJECT_ID] + cloudstorage_client.region = GCP_US_CENTER1_LOCATION + + cloudstorage_client.buckets = [ + Bucket( + name="transition-delete", + id="transition-delete", + region=GCP_US_CENTER1_LOCATION, + uniform_bucket_level_access=True, + public=False, + retention_policy=None, + project_id=GCP_PROJECT_ID, + lifecycle_rules=[ + { + "action": { + "type": "SetStorageClass", + "storageClass": "NEARLINE", + }, + "condition": {"matchesStorageClass": ["STANDARD"]}, + }, + {"action": {"type": "Delete"}, "condition": {"age": 365}}, + ], + ) + ] + + check = cloudstorage_bucket_lifecycle_management_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Bucket {cloudstorage_client.buckets[0].name} has lifecycle management enabled with 2 valid rule(s)." + ) + assert result[0].resource_id == "transition-delete" + assert result[0].resource_name == "transition-delete" + assert result[0].location == GCP_US_CENTER1_LOCATION + assert result[0].project_id == GCP_PROJECT_ID + + def test_bucket_with_invalid_lifecycle_rules(self): + cloudstorage_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_gcp_provider(), + ), + mock.patch( + "prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_client", + new=cloudstorage_client, + ), + ): + from prowler.providers.gcp.services.cloudstorage.cloudstorage_bucket_lifecycle_management_enabled.cloudstorage_bucket_lifecycle_management_enabled import ( + cloudstorage_bucket_lifecycle_management_enabled, + ) + from prowler.providers.gcp.services.cloudstorage.cloudstorage_service import ( + Bucket, + ) + + cloudstorage_client.project_ids = [GCP_PROJECT_ID] + cloudstorage_client.region = GCP_US_CENTER1_LOCATION + + cloudstorage_client.buckets = [ + Bucket( + name="invalid-rules", + id="invalid-rules", + region=GCP_US_CENTER1_LOCATION, + uniform_bucket_level_access=True, + public=False, + retention_policy=None, + project_id=GCP_PROJECT_ID, + lifecycle_rules=[ + {"action": {}, "condition": {"age": 30}}, + {"action": {"type": "Delete"}, "condition": {}}, + ], + ) + ] + + check = cloudstorage_bucket_lifecycle_management_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Bucket {cloudstorage_client.buckets[0].name} has lifecycle rules configured but none are valid." + ) + assert result[0].resource_id == "invalid-rules" + assert result[0].resource_name == "invalid-rules" + assert result[0].location == GCP_US_CENTER1_LOCATION + assert result[0].project_id == GCP_PROJECT_ID From a47f6444f8220822a2fca3e7b4219a18c6553118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:45:38 +0200 Subject: [PATCH 40/57] chore(github): improve conflicts checker action (#8980) --- .github/workflows/pr-conflict-checker.yml | 156 ++++++++-------------- 1 file changed, 52 insertions(+), 104 deletions(-) diff --git a/.github/workflows/pr-conflict-checker.yml b/.github/workflows/pr-conflict-checker.yml index 77280d5136..3761d252a3 100644 --- a/.github/workflows/pr-conflict-checker.yml +++ b/.github/workflows/pr-conflict-checker.yml @@ -1,42 +1,40 @@ -name: Prowler - PR Conflict Checker +name: 'Tools: PR Conflict Checker' on: - pull_request: + pull_request_target: types: - - opened - - synchronize - - reopened + - 'opened' + - 'synchronize' + - 'reopened' branches: - - "master" - - "v5.*" - # Leaving this commented until we find a way to run it for forks but in Prowler's context - # pull_request_target: - # types: - # - opened - # - synchronize - # - reopened - # branches: - # - "master" - # - "v5.*" + - 'master' + - 'v5.*' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: - conflict-checker: + check-conflicts: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read pull-requests: write issues: write steps: - - name: Checkout repository + - name: Checkout PR head uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 - name: Get changed files id: changed-files uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 with: - files: | - ** + files: '**' - name: Check for conflict markers id: conflict-check @@ -51,10 +49,10 @@ jobs: if [ -f "$file" ]; then echo "Checking file: $file" - # Look for conflict markers - if grep -l "^<<<<<<<\|^=======\|^>>>>>>>" "$file" 2>/dev/null; then + # Look for conflict markers (more precise regex) + if grep -qE '^(<<<<<<<|=======|>>>>>>>)' "$file" 2>/dev/null; then echo "Conflict markers found in: $file" - CONFLICT_FILES="$CONFLICT_FILES$file " + CONFLICT_FILES="${CONFLICT_FILES}- \`${file}\`"$'\n' HAS_CONFLICTS=true fi fi @@ -62,114 +60,64 @@ jobs: if [ "$HAS_CONFLICTS" = true ]; then echo "has_conflicts=true" >> $GITHUB_OUTPUT - echo "conflict_files=$CONFLICT_FILES" >> $GITHUB_OUTPUT - echo "Conflict markers detected in files: $CONFLICT_FILES" + { + echo "conflict_files<> $GITHUB_OUTPUT + echo "Conflict markers detected" else echo "has_conflicts=false" >> $GITHUB_OUTPUT echo "No conflict markers found in changed files" fi - - name: Add conflict label - if: steps.conflict-check.outputs.has_conflicts == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} - script: | - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }); + - name: Manage conflict label + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }} + run: | + LABEL_NAME="has-conflicts" - const hasConflictLabel = labels.some(label => label.name === 'has-conflicts'); + # Add or remove label based on conflict status + if [ "$HAS_CONFLICTS" = "true" ]; then + echo "Adding conflict label to PR #${PR_NUMBER}..." + gh pr edit "$PR_NUMBER" --add-label "$LABEL_NAME" --repo ${{ github.repository }} || true + else + echo "Removing conflict label from PR #${PR_NUMBER}..." + gh pr edit "$PR_NUMBER" --remove-label "$LABEL_NAME" --repo ${{ github.repository }} || true + fi - if (!hasConflictLabel) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: ['has-conflicts'] - }); - console.log('Added has-conflicts label'); - } else { - console.log('has-conflicts label already exists'); - } - - - name: Remove conflict label - if: steps.conflict-check.outputs.has_conflicts == 'false' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} - script: | - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - name: 'has-conflicts' - }); - console.log('Removed has-conflicts label'); - } catch (error) { - if (error.status === 404) { - console.log('has-conflicts label was not present'); - } else { - throw error; - } - } - - - name: Find existing conflict comment - if: steps.conflict-check.outputs.has_conflicts == 'true' + - name: Find existing comment uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-regex: '(⚠️ \*\*Conflict Markers Detected\*\*|✅ \*\*Conflict Markers Resolved\*\*)' + body-includes: '' - - name: Create or update conflict comment - if: steps.conflict-check.outputs.has_conflicts == 'true' + - name: Create or update comment uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} edit-mode: replace body: | - ⚠️ **Conflict Markers Detected** + + ${{ steps.conflict-check.outputs.has_conflicts == 'true' && '⚠️ **Conflict Markers Detected**' || '✅ **Conflict Markers Resolved**' }} - This pull request contains unresolved conflict markers in the following files: - ``` - ${{ steps.conflict-check.outputs.conflict_files }} - ``` + ${{ steps.conflict-check.outputs.has_conflicts == 'true' && format('This pull request contains unresolved conflict markers in the following files: + + {0} Please resolve these conflicts by: 1. Locating the conflict markers: `<<<<<<<`, `=======`, and `>>>>>>>` 2. Manually editing the files to resolve the conflicts 3. Removing all conflict markers - 4. Committing and pushing the changes - - - name: Find existing conflict comment when resolved - if: steps.conflict-check.outputs.has_conflicts == 'false' - uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 - id: find-resolved-comment - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-regex: '(⚠️ \*\*Conflict Markers Detected\*\*|✅ \*\*Conflict Markers Resolved\*\*)' - - - name: Update comment when conflicts resolved - if: steps.conflict-check.outputs.has_conflicts == 'false' && steps.find-resolved-comment.outputs.comment-id != '' - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - comment-id: ${{ steps.find-resolved-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - edit-mode: replace - body: | - ✅ **Conflict Markers Resolved** - - All conflict markers have been successfully resolved in this pull request. + 4. Committing and pushing the changes', steps.conflict-check.outputs.conflict_files) || 'All conflict markers have been successfully resolved in this pull request.' }} - name: Fail workflow if conflicts detected if: steps.conflict-check.outputs.has_conflicts == 'true' run: | - echo "::error::Workflow failed due to conflict markers in files: ${{ steps.conflict-check.outputs.conflict_files }}" + echo "::error::Workflow failed due to conflict markers detected in the PR" exit 1 From f5cccecac67260e293ee20a79fcb7519fddb4f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:02:51 +0200 Subject: [PATCH 41/57] chore(github): improve prepare release action (#8981) --- ...reparation.yml => prowler-prepare-release.yml} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename .github/workflows/{prowler-release-preparation.yml => prowler-prepare-release.yml} (98%) diff --git a/.github/workflows/prowler-release-preparation.yml b/.github/workflows/prowler-prepare-release.yml similarity index 98% rename from .github/workflows/prowler-release-preparation.yml rename to .github/workflows/prowler-prepare-release.yml index 7ecd937554..33f554d898 100644 --- a/.github/workflows/prowler-release-preparation.yml +++ b/.github/workflows/prowler-prepare-release.yml @@ -1,6 +1,6 @@ -name: Prowler - Release Preparation +name: 'Tools: Prepare Release' -run-name: Prowler Release Preparation for ${{ inputs.prowler_version }} +run-name: 'Prepare Release for Prowler ${{ inputs.prowler_version }}' on: workflow_dispatch: @@ -10,18 +10,23 @@ on: required: true type: string +concurrency: + group: ${{ github.workflow }}-${{ inputs.prowler_version }} + cancel-in-progress: false + env: - PROWLER_VERSION: ${{ github.event.inputs.prowler_version }} + PROWLER_VERSION: ${{ inputs.prowler_version }} jobs: prepare-release: if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: write pull-requests: write steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 @@ -34,7 +39,7 @@ jobs: - name: Install Poetry run: | - python3 -m pip install --user poetry + python3 -m pip install --user poetry==2.1.1 echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Configure Git From 52ed92ac6a692640b7d2be03033a1bd35380adb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:17:22 +0200 Subject: [PATCH 42/57] chore(github): improve check changelog action (#8983) --- .github/workflows/pr-check-changelog.yml | 103 ++++++++++++++++++ .../pull-request-check-changelog.yml | 77 ------------- 2 files changed, 103 insertions(+), 77 deletions(-) create mode 100644 .github/workflows/pr-check-changelog.yml delete mode 100644 .github/workflows/pull-request-check-changelog.yml diff --git a/.github/workflows/pr-check-changelog.yml b/.github/workflows/pr-check-changelog.yml new file mode 100644 index 0000000000..f8de212e2a --- /dev/null +++ b/.github/workflows/pr-check-changelog.yml @@ -0,0 +1,103 @@ +name: 'Tools: Check Changelog' + +on: + pull_request: + types: + - 'opened' + - 'synchronize' + - 'reopened' + - 'labeled' + - 'unlabeled' + branches: + - 'master' + - 'v5.*' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + check-changelog: + if: contains(github.event.pull_request.labels.*.name, 'no-changelog') == false + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + env: + MONITORED_FOLDERS: 'api ui prowler mcp_server' + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 + with: + files: | + api/** + ui/** + prowler/** + mcp_server/** + + - name: Check for folder changes and changelog presence + id: check-folders + run: | + missing_changelogs="" + + # Check api folder + if [[ "${{ steps.changed-files.outputs.any_changed }}" == "true" ]]; then + for folder in $MONITORED_FOLDERS; do + # Get files changed in this folder + changed_in_folder=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | tr ' ' '\n' | grep "^${folder}/" || true) + + if [ -n "$changed_in_folder" ]; then + echo "Detected changes in ${folder}/" + + # Check if CHANGELOG.md was updated + if ! echo "$changed_in_folder" | grep -q "^${folder}/CHANGELOG.md$"; then + echo "No changelog update found for ${folder}/" + missing_changelogs="${missing_changelogs}- \`${folder}\`"$'\n' + fi + fi + done + fi + + { + echo "missing_changelogs<> $GITHUB_OUTPUT + + - name: Find existing changelog comment + if: github.event.pull_request.head.repo.full_name == github.repository + id: find-comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Update PR comment with changelog status + if: github.event.pull_request.head.repo.full_name == github.repository + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + edit-mode: replace + body: | + + ${{ steps.check-folders.outputs.missing_changelogs != '' && format('⚠️ **Changes detected in the following folders without a corresponding update to the `CHANGELOG.md`:** + + {0} + + Please add an entry to the corresponding `CHANGELOG.md` file to maintain a clear history of changes.', steps.check-folders.outputs.missing_changelogs) || '✅ All necessary `CHANGELOG.md` files have been updated.' }} + + - name: Fail if changelog is missing + if: steps.check-folders.outputs.missing_changelogs != '' + run: | + echo "::error::Missing changelog updates in some folders" + exit 1 diff --git a/.github/workflows/pull-request-check-changelog.yml b/.github/workflows/pull-request-check-changelog.yml deleted file mode 100644 index 3b96e6d499..0000000000 --- a/.github/workflows/pull-request-check-changelog.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Prowler - Check Changelog - -on: - pull_request: - types: [opened, synchronize, reopened, labeled, unlabeled] - -jobs: - check-changelog: - if: contains(github.event.pull_request.labels.*.name, 'no-changelog') == false - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - pull-requests: write - env: - MONITORED_FOLDERS: "api ui prowler mcp_server" - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - fetch-depth: 0 - - - name: Get list of changed files - id: changed_files - run: | - git fetch origin ${{ github.base_ref }} - git diff --name-only origin/${{ github.base_ref }}...HEAD > changed_files.txt - cat changed_files.txt - - - name: Check for folder changes and changelog presence - id: check_folders - run: | - missing_changelogs="" - - for folder in $MONITORED_FOLDERS; do - if grep -q "^${folder}/" changed_files.txt; then - echo "Detected changes in ${folder}/" - if ! grep -q "^${folder}/CHANGELOG.md$" changed_files.txt; then - echo "No changelog update found for ${folder}/" - missing_changelogs="${missing_changelogs}- \`${folder}\`\n" - fi - fi - done - - echo "missing_changelogs<> $GITHUB_OUTPUT - echo -e "${missing_changelogs}" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Find existing changelog comment - if: github.event.pull_request.head.repo.full_name == github.repository - id: find_comment - uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad #v4.0.0 - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: '' - - - name: Update PR comment with changelog status - if: github.event.pull_request.head.repo.full_name == github.repository - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - issue-number: ${{ github.event.pull_request.number }} - comment-id: ${{ steps.find_comment.outputs.comment-id }} - edit-mode: replace - body: | - - ${{ steps.check_folders.outputs.missing_changelogs != '' && format('⚠️ **Changes detected in the following folders without a corresponding update to the `CHANGELOG.md`:** - - {0} - - Please add an entry to the corresponding `CHANGELOG.md` file to maintain a clear history of changes.', steps.check_folders.outputs.missing_changelogs) || '✅ All necessary `CHANGELOG.md` files have been updated. Great job! 🎉' }} - - - name: Fail if changelog is missing - if: steps.check_folders.outputs.missing_changelogs != '' - run: | - echo "ERROR: Missing changelog updates in some folders." - exit 1 From 2039a5005cf843f9b733fa6db7fb88667a62eeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:29:22 +0200 Subject: [PATCH 43/57] chore(github): rename prepare release action (#8985) --- ...repare-release.yml => prepare-release.yml} | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) rename .github/workflows/{prowler-prepare-release.yml => prepare-release.yml} (95%) diff --git a/.github/workflows/prowler-prepare-release.yml b/.github/workflows/prepare-release.yml similarity index 95% rename from .github/workflows/prowler-prepare-release.yml rename to .github/workflows/prepare-release.yml index 33f554d898..7aeb7a90e6 100644 --- a/.github/workflows/prowler-prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -19,7 +19,7 @@ env: jobs: prepare-release: - if: github.repository == 'prowler-cloud/prowler' + if: github.event_name == 'workflow_dispatch' && github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest timeout-minutes: 30 permissions: @@ -44,10 +44,10 @@ jobs: - name: Configure Git run: | - git config --global user.name "prowler-bot" - git config --global user.email "179230569+prowler-bot@users.noreply.github.com" + git config --global user.name 'prowler-bot' + git config --global user.email '179230569+prowler-bot@users.noreply.github.com' - - name: Parse version and determine branch + - name: Parse version and read changelogs run: | # Validate version format (reusing pattern from sdk-bump-version.yml) if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then @@ -124,7 +124,7 @@ jobs: exit 1 fi - - name: Extract changelog entries + - name: Extract and combine changelog entries run: | set -e @@ -250,7 +250,7 @@ jobs: echo "Combined changelog preview:" cat combined_changelog.md - - name: Checkout existing branch for patch release + - name: Checkout release branch for patch release if: ${{ env.PATCH_VERSION != '0' }} run: | echo "Patch release detected, checking out existing branch $BRANCH_NAME..." @@ -265,7 +265,7 @@ jobs: exit 1 fi - - name: Verify version in pyproject.toml + - name: Verify SDK version in pyproject.toml run: | CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)"/\1/' | tr -d '[:space:]') PROWLER_VERSION_TRIMMED=$(echo "$PROWLER_VERSION" | tr -d '[:space:]') @@ -275,7 +275,7 @@ jobs: fi echo "✓ pyproject.toml version: $CURRENT_VERSION" - - name: Verify version in prowler/config/config.py + - name: Verify SDK version in prowler/config/config.py run: | CURRENT_VERSION=$(grep '^prowler_version = ' prowler/config/config.py | sed -E 's/prowler_version = "([^"]+)"/\1/' | tr -d '[:space:]') PROWLER_VERSION_TRIMMED=$(echo "$PROWLER_VERSION" | tr -d '[:space:]') @@ -285,7 +285,7 @@ jobs: fi echo "✓ prowler/config/config.py version: $CURRENT_VERSION" - - name: Verify version in api/pyproject.toml + - name: Verify API version in api/pyproject.toml if: ${{ env.HAS_API_CHANGES == 'true' }} run: | CURRENT_API_VERSION=$(grep '^version = ' api/pyproject.toml | sed -E 's/version = "([^"]+)"/\1/' | tr -d '[:space:]') @@ -296,7 +296,7 @@ jobs: fi echo "✓ api/pyproject.toml version: $CURRENT_API_VERSION" - - name: Verify prowler dependency in api/pyproject.toml + - name: Verify API prowler dependency in api/pyproject.toml if: ${{ env.PATCH_VERSION != '0' && env.HAS_API_CHANGES == 'true' }} run: | CURRENT_PROWLER_REF=$(grep 'prowler @ git+https://github.com/prowler-cloud/prowler.git@' api/pyproject.toml | sed -E 's/.*@([^"]+)".*/\1/' | tr -d '[:space:]') @@ -307,7 +307,7 @@ jobs: fi echo "✓ api/pyproject.toml prowler dependency: $CURRENT_PROWLER_REF" - - name: Verify version in api/src/backend/api/v1/views.py + - name: Verify API version in api/src/backend/api/v1/views.py if: ${{ env.HAS_API_CHANGES == 'true' }} run: | CURRENT_API_VERSION=$(grep 'spectacular_settings.VERSION = ' api/src/backend/api/v1/views.py | sed -E 's/.*spectacular_settings.VERSION = "([^"]+)".*/\1/' | tr -d '[:space:]') @@ -318,7 +318,7 @@ jobs: fi echo "✓ api/src/backend/api/v1/views.py version: $CURRENT_API_VERSION" - - name: Checkout existing release branch for minor release + - name: Checkout release branch for minor release if: ${{ env.PATCH_VERSION == '0' }} run: | echo "Minor release detected (patch = 0), checking out existing branch $BRANCH_NAME..." @@ -330,7 +330,7 @@ jobs: exit 1 fi - - name: Prepare prowler dependency update for minor release + - name: Update API prowler dependency for minor release if: ${{ env.PATCH_VERSION == '0' }} run: | CURRENT_PROWLER_REF=$(grep 'prowler @ git+https://github.com/prowler-cloud/prowler.git@' api/pyproject.toml | sed -E 's/.*@([^"]+)".*/\1/' | tr -d '[:space:]') @@ -367,7 +367,7 @@ jobs: echo "✓ Prepared prowler dependency update to: $UPDATED_PROWLER_REF" - - name: Create Pull Request against release branch + - name: Create PR for API dependency update if: ${{ env.PATCH_VERSION == '0' }} uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: From b7ce9ae5f31ce4056c61cd45d2c35804bc717109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:35:38 +0200 Subject: [PATCH 44/57] chore(github): improve mcp container action (#8986) --- .../workflows/mcp-container-build-push.yml | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/mcp-container-build-push.yml b/.github/workflows/mcp-container-build-push.yml index fa4f5be578..aecec30592 100644 --- a/.github/workflows/mcp-container-build-push.yml +++ b/.github/workflows/mcp-container-build-push.yml @@ -3,21 +3,13 @@ name: 'MCP: Container Build and Push' on: push: branches: - - "master" + - 'master' paths: - - "mcp_server/**" - - ".github/workflows/mcp-container-build-push.yml" - - # Uncomment to test this workflow on PRs - # pull_request: - # branches: - # - "master" - # paths: - # - "mcp_server/**" - # - ".github/workflows/mcp-container-build-push.yml" - + - 'mcp_server/**' + - '.github/workflows/mcp-container-build-push.yml' release: - types: [published] + types: + - 'published' permissions: contents: read @@ -41,6 +33,7 @@ jobs: setup: if: github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest + timeout-minutes: 5 outputs: short-sha: ${{ steps.set-short-sha.outputs.short-sha }} steps: @@ -51,8 +44,12 @@ jobs: container-build-push: needs: setup runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: write steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Login to DockerHub @@ -64,7 +61,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - - name: Build and push container (latest) + - name: Build and push MCP container (latest) if: github.event_name == 'push' uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: @@ -83,7 +80,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Build and push container (release) + - name: Build and push MCP container (release) if: github.event_name == 'release' uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: @@ -103,7 +100,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Trigger deployment + - name: Trigger MCP deployment if: github.event_name == 'push' uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 with: From fdf45ea777a71df8a6c172cf07dfbf9992029a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:52:00 +0200 Subject: [PATCH 45/57] chore(github): improve pr merged action (#8987) --- ...{pull-request-merged.yml => pr-merged.yml} | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) rename .github/workflows/{pull-request-merged.yml => pr-merged.yml} (52%) diff --git a/.github/workflows/pull-request-merged.yml b/.github/workflows/pr-merged.yml similarity index 52% rename from .github/workflows/pull-request-merged.yml rename to .github/workflows/pr-merged.yml index 4b5a93aabd..da8e359adb 100644 --- a/.github/workflows/pull-request-merged.yml +++ b/.github/workflows/pr-merged.yml @@ -1,27 +1,31 @@ -name: Prowler - Merged Pull Request +name: 'Tools: PR Merged' on: pull_request_target: - branches: ['master'] - types: ['closed'] + branches: + - 'master' + types: + - 'closed' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: false jobs: trigger-cloud-pull-request: - name: Trigger Cloud Pull Request if: github.event.pull_request.merged == true && github.repository == 'prowler-cloud/prowler' runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - ref: ${{ github.event.pull_request.merge_commit_sha }} - - - name: Set short git commit SHA + - name: Calculate short commit SHA id: vars run: | - shortSha=$(git rev-parse --short ${{ github.event.pull_request.merge_commit_sha }}) - echo "SHORT_SHA=${shortSha}" >> $GITHUB_ENV + SHORT_SHA="${{ github.event.pull_request.merge_commit_sha }}" + echo "SHORT_SHA=${SHORT_SHA::7}" >> $GITHUB_ENV - - name: Trigger pull request + - name: Trigger Cloud repository pull request uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 with: token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} @@ -31,8 +35,13 @@ jobs: { "PROWLER_COMMIT_SHA": "${{ github.event.pull_request.merge_commit_sha }}", "PROWLER_COMMIT_SHORT_SHA": "${{ env.SHORT_SHA }}", + "PROWLER_PR_NUMBER": "${{ github.event.pull_request.number }}", "PROWLER_PR_TITLE": ${{ toJson(github.event.pull_request.title) }}, "PROWLER_PR_LABELS": ${{ toJson(github.event.pull_request.labels.*.name) }}, "PROWLER_PR_BODY": ${{ toJson(github.event.pull_request.body) }}, - "PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }} + "PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }}, + "PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}", + "PROWLER_PR_AUTHOR": "${{ github.event.pull_request.user.login }}", + "PROWLER_PR_BASE_BRANCH": "${{ github.event.pull_request.base.ref }}", + "PROWLER_PR_HEAD_BRANCH": "${{ github.event.pull_request.head.ref }}" } From 6184de52d9323d4c10e12441243a09e09ca57e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:05:31 +0200 Subject: [PATCH 46/57] chore(github): fix pr merged action (#8988) --- .github/workflows/pr-merged.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-merged.yml b/.github/workflows/pr-merged.yml index da8e359adb..d8255026e6 100644 --- a/.github/workflows/pr-merged.yml +++ b/.github/workflows/pr-merged.yml @@ -41,7 +41,6 @@ jobs: "PROWLER_PR_BODY": ${{ toJson(github.event.pull_request.body) }}, "PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }}, "PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}", - "PROWLER_PR_AUTHOR": "${{ github.event.pull_request.user.login }}", "PROWLER_PR_BASE_BRANCH": "${{ github.event.pull_request.base.ref }}", "PROWLER_PR_HEAD_BRANCH": "${{ github.event.pull_request.head.ref }}" } From 4b160257b90f3fec702a81f65e9a9dcc8fb41d17 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:26:58 +0200 Subject: [PATCH 47/57] chore(sdk): update changelog for v5.13.0 (#8989) --- prowler/CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 2b5bdfd42a..e4284da154 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -52,12 +52,6 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add missing attributes for Mitre Attack AWS, Azure and GCP [(#8907)](https://github.com/prowler-cloud/prowler/pull/8907) - Fix KeyError in CloudSQL and Monitoring services in GCP provider [(#8909)](https://github.com/prowler-cloud/prowler/pull/8909) - Fix ResourceName in GCP provider [(#8928)](https://github.com/prowler-cloud/prowler/pull/8928) - ---- - -## [v5.12.4] (Prowler UNRELEASED) - -### Fixed - Fix KeyError in `elb_ssl_listeners_use_acm_certificate` check and handle None cluster version in `eks_cluster_uses_a_supported_version` check [(#8791)](https://github.com/prowler-cloud/prowler/pull/8791) - Fix file extension parsing for compliance reports [(#8791)](https://github.com/prowler-cloud/prowler/pull/8791) - Added user pagination to Entra and Admincenter services [(#8858)](https://github.com/prowler-cloud/prowler/pull/8858) From 8d4fa46038130dd5b5384bcd794ebde4e314e604 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:23:14 +0200 Subject: [PATCH 48/57] chore: script to generate AWS accounts list from AWS Org for bulk provisioning (#8903) --- docs/docs.json | 3 +- .../aws-organizations-bulk-provisioning.mdx | 491 ++++++++++++++++++ .../tutorials/bulk-provider-provisioning.mdx | 37 +- util/prowler-bulk-provisioning/README.md | 116 ++++- .../aws_org_generator.py | 333 ++++++++++++ .../examples/aws-org-example.yaml | 49 ++ .../nuke_providers.py | 28 +- .../prowler_bulk_provisioning.py | 26 +- .../requirements-aws-org.txt | 11 + 9 files changed, 1029 insertions(+), 65 deletions(-) create mode 100644 docs/user-guide/tutorials/aws-organizations-bulk-provisioning.mdx create mode 100755 util/prowler-bulk-provisioning/aws_org_generator.py create mode 100644 util/prowler-bulk-provisioning/examples/aws-org-example.yaml create mode 100644 util/prowler-bulk-provisioning/requirements-aws-org.txt diff --git a/docs/docs.json b/docs/docs.json index fb7e91a774..ea64490532 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -114,7 +114,8 @@ "group": "Tutorials", "pages": [ "user-guide/tutorials/prowler-app-sso-entra", - "user-guide/tutorials/bulk-provider-provisioning" + "user-guide/tutorials/bulk-provider-provisioning", + "user-guide/tutorials/aws-organizations-bulk-provisioning" ] } ] diff --git a/docs/user-guide/tutorials/aws-organizations-bulk-provisioning.mdx b/docs/user-guide/tutorials/aws-organizations-bulk-provisioning.mdx new file mode 100644 index 0000000000..eb513096a4 --- /dev/null +++ b/docs/user-guide/tutorials/aws-organizations-bulk-provisioning.mdx @@ -0,0 +1,491 @@ +--- +title: 'AWS Organizations Bulk Provisioning in Prowler' +--- + +Prowler offers an automated tool to discover and provision all AWS accounts within an AWS Organization. This streamlines onboarding for organizations managing multiple AWS accounts by automatically generating the configuration needed for bulk provisioning. + +The tool, `aws_org_generator.py`‎, complements the [Bulk Provider Provisioning](./bulk-provider-provisioning) tool and is available in the Prowler repository at: [util/prowler-bulk-provisioning](https://github.com/prowler-cloud/prowler/tree/master/util/prowler-bulk-provisioning) + + +Native support for bulk provisioning AWS Organizations and similar multi-account structures directly in the Prowler UI/API is on the official roadmap. + +Track progress and vote for this feature at: [Bulk Provisioning in the UI/API for AWS Organizations](https://roadmap.prowler.com/p/builk-provisioning-in-the-uiapi-for-aws-organizations-and-alike) + + +{/* TODO: Add screenshot of the tool in action */} + +## Overview + +The AWS Organizations Bulk Provisioning tool simplifies multi-account onboarding by: + +* Automatically discovering all active accounts in an AWS Organization +* Generating YAML configuration files for bulk provisioning +* Supporting account filtering and custom role configurations +* Eliminating manual entry of account IDs and role ARNs + +## Prerequisites + +### Requirements + +* Python 3.7 or higher +* AWS credentials with Organizations read access +* ProwlerRole (or custom role) deployed across all target accounts +* Prowler API key (from Prowler Cloud or self-hosted Prowler App) + * For self-hosted Prowler App, remember to [point to your API base URL](./bulk-provider-provisioning#custom-api-endpoints) + * Learn how to create API keys: [Prowler App API Keys](../providers/prowler-app-api-keys) + +### Deploying ProwlerRole Across AWS Organizations + +Before using the AWS Organizations generator, deploy the ProwlerRole across all accounts in the organization using CloudFormation StackSets. + + +**Follow the official documentation:** +[Deploying Prowler IAM Roles Across AWS Organizations](../providers/aws/organizations#deploying-prowler-iam-roles-across-aws-organizations) + +**Key points:** + +* Use CloudFormation StackSets from the management account +* Deploy to all organizational units (OUs) or specific OUs +* Use an external ID for enhanced security +* Ensure the role has necessary permissions for Prowler scans + + +### Installation + +Clone the repository and install required dependencies: + +```bash +git clone https://github.com/prowler-cloud/prowler.git +cd prowler/util/prowler-bulk-provisioning +pip install -r requirements-aws-org.txt +``` + +### AWS Credentials Setup + +Configure AWS credentials with Organizations read access: + +* **Management account credentials**, or +* **Delegated administrator account** with `organizations:ListAccounts` permission + +Required IAM permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "organizations:ListAccounts", + "organizations:DescribeOrganization" + ], + "Resource": "*" + } + ] +} +``` + +### Prowler API Key Setup + +Configure your Prowler API key: + +```bash +export PROWLER_API_KEY="pk_example-api-key" +``` + +To create an API key: + +1. Log in to Prowler Cloud or Prowler App +2. Click **Profile** → **Account** +3. Click **Create API Key** +4. Provide a descriptive name and optionally set an expiration date +5. Copy the generated API key (it will only be shown once) + +For detailed instructions, see: [Prowler App API Keys](../providers/prowler-app-api-keys) + +## Basic Usage + +### Generate Configuration for All Accounts + +To generate a YAML configuration file for all active accounts in the organization: + +```bash +python aws_org_generator.py -o aws-accounts.yaml --external-id prowler-ext-id-2024 +``` + +This command: + +1. Lists all ACTIVE accounts in the organization +2. Generates YAML entries for each account +3. Saves the configuration to `aws-accounts.yaml` + +**Output:** + +``` +Fetching accounts from AWS Organizations... +Found 47 active accounts in organization +Generated configuration for 47 accounts + +Configuration written to: aws-accounts.yaml + +Next steps: + 1. Review the generated file: cat aws-accounts.yaml | head -n 20 + 2. Run bulk provisioning: python prowler_bulk_provisioning.py aws-accounts.yaml +``` + +### Review Generated Configuration + +Review the generated YAML configuration: + +```bash +head -n 20 aws-accounts.yaml +``` + +**Example output:** + +```yaml +- provider: aws + uid: '111111111111' + alias: Production-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::111111111111:role/ProwlerRole + external_id: prowler-ext-id-2024 + +- provider: aws + uid: '222222222222' + alias: Development-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::222222222222:role/ProwlerRole + external_id: prowler-ext-id-2024 +``` + +### Dry Run Mode + +Test the configuration without writing a file: + +```bash +python aws_org_generator.py \ + --external-id prowler-ext-id-2024 \ + --dry-run +``` + +## Advanced Configuration + +### Using a Specific AWS Profile + +Specify an AWS profile when multiple profiles are configured: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --profile org-management-admin \ + --external-id prowler-ext-id-2024 +``` + +### Excluding Specific Accounts + +Exclude the management account or other accounts from provisioning: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --external-id prowler-ext-id-2024 \ + --exclude 123456789012,210987654321 +``` + +Common exclusion scenarios: + +* Management account (requires different permissions) +* Break-glass accounts (emergency access) +* Suspended or archived accounts + +### Including Only Specific Accounts + +Generate configuration for specific accounts only: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --external-id prowler-ext-id-2024 \ + --include 111111111111,222222222222,333333333333 +``` + +### Custom Role Name + +Specify a custom role name if not using the default `ProwlerRole`: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --role-name ProwlerExecutionRole \ + --external-id prowler-ext-id-2024 +``` + +### Custom Alias Format + +Customize account aliases using template variables: + +```bash +# Use account name and ID +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --alias-format "{name}-{id}" \ + --external-id prowler-ext-id-2024 + +# Use email prefix +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --alias-format "{email}" \ + --external-id prowler-ext-id-2024 +``` + +Available template variables: + +* `{name}` - Account name +* `{id}` - Account ID +* `{email}` - Account email + +### Additional Role Assumption Options + +Configure optional role assumption parameters: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --role-name ProwlerRole \ + --external-id prowler-ext-id-2024 \ + --session-name prowler-scan-session \ + --duration-seconds 3600 +``` + +## Complete Workflow Example + + + + 1. Log in to the AWS management account + 2. Open CloudFormation → StackSets + 3. Create a new StackSet using the [Prowler role template](https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/cloudformation/prowler-scan-role.yml) + 4. Deploy to all organizational units + 5. Use a unique external ID (e.g., `prowler-org-2024-abc123`) + + {/* TODO: Add screenshot of CloudFormation StackSets deployment */} + + + + Configure AWS credentials and generate the YAML file: + + ```bash + # Using management account credentials + export AWS_PROFILE=org-management + + # Generate configuration + python aws_org_generator.py \ + -o aws-org-accounts.yaml \ + --external-id prowler-org-2024-abc123 \ + --exclude 123456789012 + ``` + + **Output:** + + ``` + Fetching accounts from AWS Organizations... + Using AWS profile: org-management + Found 47 active accounts in organization + Generated configuration for 46 accounts + + Configuration written to: aws-org-accounts.yaml + + Next steps: + 1. Review the generated file: cat aws-org-accounts.yaml | head -n 20 + 2. Run bulk provisioning: python prowler_bulk_provisioning.py aws-org-accounts.yaml + ``` + + + + Verify the generated YAML configuration: + + ```bash + # View first 20 lines + head -n 20 aws-org-accounts.yaml + + # Check for unexpected accounts + grep "uid:" aws-org-accounts.yaml + + # Verify role ARNs + grep "role_arn:" aws-org-accounts.yaml | head -5 + + # Count accounts + grep "provider: aws" aws-org-accounts.yaml | wc -l + ``` + + + + Provision all accounts to Prowler Cloud or Prowler App: + + ```bash + # Set Prowler API key + export PROWLER_API_KEY="pk_example-api-key" + + # Run bulk provisioning with connection testing + python prowler_bulk_provisioning.py aws-org-accounts.yaml + ``` + + **With custom options:** + + ```bash + python prowler_bulk_provisioning.py aws-org-accounts.yaml \ + --concurrency 10 \ + --timeout 120 + ``` + + **Successful output:** + + ``` + [1] ✅ Created provider (id=db9a8985-f9ec-4dd8-b5a0-e05ab3880bed) + [1] ✅ Created secret (id=466f76c6-5878-4602-a4bc-13f9522c1fd2) + [1] ✅ Connection test: Connected + + [2] ✅ Created provider (id=7a99f789-0cf5-4329-8279-2d443a962676) + [2] ✅ Created secret (id=c5702180-f7c4-40fd-be0e-f6433479b126) + [2] ✅ Connection test: Connected + + Done. Success: 47 Failures: 0 + ``` + + {/* TODO: Add screenshot of successful bulk provisioning output */} + + + +## Command Reference + +### Full Command-Line Options + +```bash +python aws_org_generator.py \ + -o OUTPUT_FILE \ + --role-name ROLE_NAME \ + --external-id EXTERNAL_ID \ + --session-name SESSION_NAME \ + --duration-seconds SECONDS \ + --alias-format FORMAT \ + --exclude ACCOUNT_IDS \ + --include ACCOUNT_IDS \ + --profile AWS_PROFILE \ + --region AWS_REGION \ + --dry-run +``` + +## Troubleshooting + +### Error: "No AWS credentials found" + +**Solution:** Configure AWS credentials using one of these methods: + +```bash +# Method 1: AWS CLI configure +aws configure + +# Method 2: Environment variables +export AWS_ACCESS_KEY_ID=your-key-id +export AWS_SECRET_ACCESS_KEY=your-secret-key + +# Method 3: Use AWS profile +export AWS_PROFILE=org-management +``` + +### Error: "Access denied to AWS Organizations API" + +**Cause:** Current credentials don't have permission to list organization accounts. + +**Solution:** + +* Ensure management account credentials are used +* Verify IAM permissions include `organizations:ListAccounts` +* Check IAM policies for Organizations access + +### Error: "AWS Organizations is not enabled" + +**Cause:** The account is not part of an organization. + +**Solution:** This tool requires an AWS Organization. Create one in the AWS Organizations console or use standard bulk provisioning for standalone accounts. + +### No Accounts Generated After Filters + +**Cause:** All accounts were filtered out by `--exclude` or `--include` options. + +**Solution:** Review filter options and verify account IDs are correct: + +```bash +# List all accounts in organization +aws organizations list-accounts --query "Accounts[?Status=='ACTIVE'].[Id,Name]" --output table +``` + +### Connection Test Failures During Bulk Provisioning + +**Cause:** ProwlerRole may not be deployed correctly or credentials are invalid. + +**Solution:** + +* Verify StackSet deployment status in CloudFormation +* Check role trust policy includes correct external ID +* Test role assumption manually: + +```bash +aws sts assume-role \ + --role-arn arn:aws:iam::123456789012:role/ProwlerRole \ + --role-session-name test \ + --external-id prowler-ext-id-2024 +``` + +## Security Best Practices + +### Use External ID + +Always use an external ID when assuming cross-account roles: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --external-id $(uuidgen | tr '[:upper:]' '[:lower:]') +``` + +The external ID must match the one configured in the ProwlerRole trust policy across all accounts. + +### Exclude Sensitive Accounts + +Exclude accounts that shouldn't be scanned or require special handling: + +```bash +python aws_org_generator.py \ + -o aws-accounts.yaml \ + --external-id prowler-ext-id \ + --exclude 123456789012,111111111111 # management, break-glass accounts +``` + +### Review Generated Configuration + +Always review the generated YAML before provisioning: + +```bash +# Check for unexpected accounts +grep "uid:" aws-org-accounts.yaml + +# Verify role ARNs +grep "role_arn:" aws-org-accounts.yaml | head -5 + +# Count accounts +grep "provider: aws" aws-org-accounts.yaml | wc -l +``` + +## Next Steps + + + + Learn how to bulk provision providers in Prowler. + + + Detailed instructions on how to use Prowler. + + diff --git a/docs/user-guide/tutorials/bulk-provider-provisioning.mdx b/docs/user-guide/tutorials/bulk-provider-provisioning.mdx index 11e7f14cf8..e81e4fae4d 100644 --- a/docs/user-guide/tutorials/bulk-provider-provisioning.mdx +++ b/docs/user-guide/tutorials/bulk-provider-provisioning.mdx @@ -17,14 +17,18 @@ The Bulk Provider Provisioning tool automates the creation of cloud providers in * Testing connections to verify successful authentication * Processing multiple providers concurrently for efficiency + +**Using AWS Organizations?** For organizations with many AWS accounts, use the automated [AWS Organizations Bulk Provisioning](./aws-organizations-bulk-provisioning) tool to automatically discover and generate configuration for all accounts in your organization. + ## Prerequisites ### Requirements * Python 3.7 or higher -* Prowler API token (from Prowler Cloud or self-hosted Prowler App) +* Prowler API key (from Prowler Cloud or self-hosted Prowler App) * For self-hosted Prowler App, remember to [point to your API base URL](#custom-api-endpoints) + * Learn how to create API keys: [Prowler App API Keys](../providers/prowler-app-api-keys) * Authentication credentials for target cloud providers ### Installation @@ -39,28 +43,21 @@ pip install -r requirements.txt ### Authentication Setup -Configure your Prowler API token: +Configure your Prowler API key: ```bash -export PROWLER_API_TOKEN="your-prowler-api-token" +export PROWLER_API_KEY="pk_example-api-key" ``` -To obtain an API token programmatically: +To create an API key: -```bash -export PROWLER_API_TOKEN=$(curl --location 'https://api.prowler.com/api/v1/tokens' \ - --header 'Content-Type: application/vnd.api+json' \ - --header 'Accept: application/vnd.api+json' \ - --data-raw '{ - "data": { - "type": "tokens", - "attributes": { - "email": "your@email.com", - "password": "your-password" - } - } - }' | jq -r .data.attributes.access) -``` +1. Log in to Prowler Cloud or Prowler App +2. Click **Profile** → **Account** +3. Click **Create API Key** +4. Provide a descriptive name and optionally set an expiration date +5. Copy the generated API key (it will only be shown once) + +For detailed instructions, see: [Prowler App API Keys](../providers/prowler-app-api-keys) ## Configuration File Structure @@ -340,11 +337,11 @@ Done. Success: 2 Failures: 0 ## Troubleshooting -### Invalid API Token +### Invalid API Key ``` Error: 401 Unauthorized -Solution: Verify your PROWLER_API_TOKEN or --token parameter +Solution: Verify your PROWLER_API_KEY environment variable or --api-key parameter ``` ### Network Timeouts diff --git a/util/prowler-bulk-provisioning/README.md b/util/prowler-bulk-provisioning/README.md index 3851979248..4cdb020e21 100644 --- a/util/prowler-bulk-provisioning/README.md +++ b/util/prowler-bulk-provisioning/README.md @@ -19,6 +19,7 @@ A Python script to bulk-provision cloud providers in Prowler Cloud/App via REST - **Flexible Authentication:** Supports various authentication methods per provider - **Error Handling:** Comprehensive error reporting and validation - **Connection Testing:** Built-in provider connection verification +- **AWS Organizations Support:** Automated YAML generation for all accounts in an AWS Organization ## How It Works @@ -48,32 +49,105 @@ This two-step approach follows the Prowler API design where providers and their pip install -r requirements.txt ``` -3. Get your Prowler API token: - - **Prowler Cloud:** Generate token at https://api.prowler.com - - **Self-hosted Prowler App:** Generate token in your local instance +3. Get your Prowler API key: + - **Prowler Cloud:** Create an API key at https://api.prowler.com + - **Self-hosted Prowler App:** Create an API key in your local instance + - Click **Profile** → **Account** → **Create API Key** ```bash - export PROWLER_API_TOKEN=$(curl --location 'https://api.prowler.com/api/v1/tokens' \ - --header 'Content-Type: application/vnd.api+json' \ - --header 'Accept: application/vnd.api+json' \ - --data-raw '{ - "data": { - "type": "tokens", - "attributes": { - "email": "your@email.com", - "password": "your-password" - } - } - }' | jq -r .data.attributes.access) + export PROWLER_API_KEY="pk_example-api-key" ``` + For detailed instructions on creating API keys, see: https://docs.prowler.com/user-guide/providers/prowler-app-api-keys + + +## AWS Organizations Integration + +For organizations with many AWS accounts, use the included `aws_org_generator.py` script to automatically generate configuration for all accounts in your AWS Organization. + +**📖 Full Guide:** See [AWS Organizations Bulk Provisioning Tutorial](https://docs.prowler.com/user-guide/tutorials/aws-organizations-bulk-provisioning) for complete documentation, examples, and troubleshooting. + +### Prerequisites + +Before using the AWS Organizations generator, deploy the ProwlerRole across all accounts using CloudFormation StackSets: + +**Documentation:** [Deploying Prowler IAM Roles Across AWS Organizations](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/aws/organizations/#deploying-prowler-iam-roles-across-aws-organizations) + +### Quick Start + +1. Install additional dependencies: + ```bash + pip install -r requirements-aws-org.txt + ``` + +2. Generate YAML configuration for all organization accounts: + ```bash + python aws_org_generator.py -o aws-accounts.yaml --external-id example-external-id + ``` + +3. Run bulk provisioning: + ```bash + python prowler_bulk_provisioning.py aws-accounts.yaml + ``` + +### AWS Organizations Generator Options + +```bash +python aws_org_generator.py -o aws-accounts.yaml \ + --role-name ProwlerRole \ + --external-id my-external-id \ + --exclude 123456789012 \ + --profile org-management +``` + +| Option | Description | Default | +|--------|-------------|---------| +| `-o, --output` | Output YAML file path | `aws-org-accounts.yaml` | +| `--role-name` | IAM role name across accounts | `ProwlerRole` | +| `--external-id` | External ID for role assumption | None (recommended) | +| `--session-name` | Session name for role assumption | None | +| `--duration-seconds` | Session duration in seconds | None | +| `--alias-format` | Alias template: `{name}`, `{id}`, `{email}` | `{name}` | +| `--exclude` | Comma-separated account IDs to exclude | None | +| `--include` | Comma-separated account IDs to include | None | +| `--profile` | AWS CLI profile name | Default credentials | +| `--region` | AWS region | `us-east-1` | +| `--dry-run` | Print to stdout without writing | `False` | + +### Examples + +**Generate config for all accounts with custom external ID:** +```bash +python aws_org_generator.py -o aws-accounts.yaml --external-id prowler-2024-abc123 +``` + +**Exclude management account:** +```bash +python aws_org_generator.py -o aws-accounts.yaml \ + --external-id prowler-ext-id \ + --exclude 123456789012 +``` + +**Use specific AWS profile:** +```bash +python aws_org_generator.py -o aws-accounts.yaml \ + --profile org-admin \ + --external-id prowler-ext-id +``` + +**Custom alias format:** +```bash +python aws_org_generator.py -o aws-accounts.yaml \ + --alias-format "{name}-{id}" \ + --external-id prowler-ext-id +``` ## Configuration ### Environment Variables ```bash -export PROWLER_API_TOKEN="your-prowler-token" +export PROWLER_API_KEY="pk_example-api-key" export PROWLER_API_BASE="https://api.prowler.com/api/v1" # Optional, defaults to Prowler Cloud ``` @@ -168,7 +242,7 @@ python prowler_bulk_provisioning.py providers.yaml \ |--------|-------------|---------| | `input_file` | YAML file with provider entries | Required | | `--base-url` | API base URL | `https://api.prowler.com/api/v1` | -| `--token` | Bearer token | `PROWLER_API_TOKEN` env var | +| `--api-key` | Prowler API key | `PROWLER_API_KEY` env var | | `--providers-endpoint` | Providers API endpoint | `/providers` | | `--concurrency` | Number of concurrent requests | `5` | | `--timeout` | Per-request timeout in seconds | `60` | @@ -241,8 +315,8 @@ The Prowler API supports the following authentication methods for GCP: # OR inline: # inline_json: # type: "service_account" - # project_id: "your-project" - # private_key_id: "key-id" + # project_id: "example-project" + # private_key_id: "example-key-id" # private_key: "-----BEGIN PRIVATE KEY-----\n..." # client_email: "service-account@project.iam.gserviceaccount.com" # client_id: "1234567890" @@ -379,10 +453,10 @@ python prowler_bulk_provisioning.py providers.yaml --dry-run ### Common Issues -1. **Invalid API Token** +1. **Invalid API Key** ``` Error: 401 Unauthorized - Solution: Check your PROWLER_API_TOKEN or --token parameter + Solution: Check your PROWLER_API_KEY environment variable or --api-key parameter ``` 2. **Network Timeouts** diff --git a/util/prowler-bulk-provisioning/aws_org_generator.py b/util/prowler-bulk-provisioning/aws_org_generator.py new file mode 100755 index 0000000000..cea3e26065 --- /dev/null +++ b/util/prowler-bulk-provisioning/aws_org_generator.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 + +""" +AWS Organizations Account Generator for Prowler Bulk Provisioning + +Generates YAML configuration for all accounts in an AWS Organization, +ready to be used with prowler_bulk_provisioning.py. + +Prerequisites: +- ProwlerRole (or custom role) must be deployed across all accounts +- AWS credentials with Organizations read access (typically management account) +- See: https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/aws/organizations/#deploying-prowler-iam-roles-across-aws-organizations +""" + +from __future__ import annotations + +import argparse +import sys +from typing import Any, Dict, List, Optional + +try: + import boto3 + from botocore.exceptions import ClientError, NoCredentialsError +except ImportError: + sys.exit( + "boto3 is required. Install with: pip install boto3\n" + "Or install all dependencies: pip install -r requirements-aws-org.txt" + ) + +try: + import yaml +except ImportError: + sys.exit("PyYAML is required. Install with: pip install pyyaml") + + +def get_org_accounts( + profile: Optional[str] = None, region: Optional[str] = None +) -> List[Dict[str, Any]]: + """ + Retrieve all accounts from AWS Organizations. + + Args: + profile: AWS CLI profile name + region: AWS region (defaults to us-east-1 for Organizations) + + Returns: + List of account dictionaries with id, name, email, and status + """ + try: + session = boto3.Session(profile_name=profile, region_name=region or "us-east-1") + client = session.client("organizations") + + accounts = [] + paginator = client.get_paginator("list_accounts") + + for page in paginator.paginate(): + for account in page["Accounts"]: + # Only include ACTIVE accounts + if account["Status"] == "ACTIVE": + accounts.append( + { + "id": account["Id"], + "name": account["Name"], + "email": account["Email"], + "status": account["Status"], + } + ) + + return accounts + + except NoCredentialsError: + sys.exit( + "No AWS credentials found. Configure credentials using:\n" + " - AWS CLI: aws configure\n" + " - Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY\n" + " - IAM role if running on EC2/ECS/Lambda" + ) + except ClientError as e: + error_code = e.response["Error"]["Code"] + if error_code == "AccessDeniedException": + sys.exit( + "Access denied to AWS Organizations API.\n" + "Ensure you are using credentials from the management account\n" + "with permissions to call organizations:ListAccounts" + ) + elif error_code == "AWSOrganizationsNotInUseException": + sys.exit( + "AWS Organizations is not enabled for this account.\n" + "This script requires an AWS Organization to be set up." + ) + else: + sys.exit(f"AWS API error: {e}") + except Exception as e: + sys.exit(f"Unexpected error listing accounts: {e}") + + +def generate_yaml_config( + accounts: List[Dict[str, Any]], + role_name: str = "ProwlerRole", + external_id: Optional[str] = None, + session_name: Optional[str] = None, + duration_seconds: Optional[int] = None, + alias_format: str = "{name}", + exclude_accounts: Optional[List[str]] = None, + include_accounts: Optional[List[str]] = None, +) -> List[Dict[str, Any]]: + """ + Generate YAML configuration for Prowler bulk provisioning. + + Args: + accounts: List of account dictionaries from get_org_accounts + role_name: IAM role name (default: ProwlerRole) + external_id: External ID for role assumption (optional but recommended) + session_name: Session name for role assumption (optional) + duration_seconds: Session duration in seconds (optional) + alias_format: Format string for alias (supports {name}, {id}, {email}) + exclude_accounts: List of account IDs to exclude + include_accounts: List of account IDs to include (if set, only these are included) + + Returns: + List of provider configurations ready for YAML export + """ + exclude_accounts = exclude_accounts or [] + include_accounts = include_accounts or [] + + providers = [] + + for account in accounts: + account_id = account["id"] + + # Apply filters + if include_accounts and account_id not in include_accounts: + continue + if account_id in exclude_accounts: + continue + + # Format alias using template + alias = alias_format.format( + name=account["name"], id=account_id, email=account["email"] + ) + + # Build role ARN + role_arn = f"arn:aws:iam::{account_id}:role/{role_name}" + + # Build credentials section + credentials: Dict[str, Any] = {"role_arn": role_arn} + + if external_id: + credentials["external_id"] = external_id + + if session_name: + credentials["session_name"] = session_name + + if duration_seconds: + credentials["duration_seconds"] = duration_seconds + + # Build provider entry + provider = { + "provider": "aws", + "uid": account_id, + "alias": alias, + "auth_method": "role", + "credentials": credentials, + } + + providers.append(provider) + + return providers + + +def main(): + """Main function to generate AWS Organizations YAML configuration.""" + parser = argparse.ArgumentParser( + description="Generate Prowler bulk provisioning YAML from AWS Organizations", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Basic usage - generate YAML for all accounts + python aws_org_generator.py -o aws-accounts.yaml + + # Use custom role name and external ID + python aws_org_generator.py -o aws-accounts.yaml \\ + --role-name ProwlerExecutionRole \\ + --external-id my-external-id-12345 + + # Use specific AWS profile + python aws_org_generator.py -o aws-accounts.yaml \\ + --profile org-management + + # Exclude specific accounts (e.g., management account) + python aws_org_generator.py -o aws-accounts.yaml \\ + --exclude 123456789012,210987654321 + + # Include only specific accounts + python aws_org_generator.py -o aws-accounts.yaml \\ + --include 111111111111,222222222222 + + # Custom alias format + python aws_org_generator.py -o aws-accounts.yaml \\ + --alias-format "{name}-{id}" + +Prerequisites: + 1. Deploy ProwlerRole across all accounts using CloudFormation StackSets: + https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/aws/organizations/#deploying-prowler-iam-roles-across-aws-organizations + + 2. Ensure AWS credentials have Organizations read access: + - organizations:ListAccounts + - organizations:DescribeOrganization (optional) + """, + ) + + parser.add_argument( + "-o", + "--output", + default="aws-org-accounts.yaml", + help="Output YAML file path (default: aws-org-accounts.yaml)", + ) + + parser.add_argument( + "--role-name", + default="ProwlerRole", + help="IAM role name deployed across accounts (default: ProwlerRole)", + ) + + parser.add_argument( + "--external-id", + help="External ID for role assumption (recommended for security)", + ) + + parser.add_argument( + "--session-name", help="Session name for role assumption (optional)" + ) + + parser.add_argument( + "--duration-seconds", + type=int, + help="Session duration in seconds (optional, default: 3600)", + ) + + parser.add_argument( + "--alias-format", + default="{name}", + help="Alias format template. Available: {name}, {id}, {email} (default: {name})", + ) + + parser.add_argument( + "--exclude", + help="Comma-separated list of account IDs to exclude", + ) + + parser.add_argument( + "--include", + help="Comma-separated list of account IDs to include (if set, only these are processed)", + ) + + parser.add_argument( + "--profile", + help="AWS CLI profile name (uses default credentials if not specified)", + ) + + parser.add_argument( + "--region", + help="AWS region (default: us-east-1, Organizations is global but needs a region)", + ) + + parser.add_argument( + "--dry-run", + action="store_true", + help="Print configuration to stdout without writing file", + ) + + args = parser.parse_args() + + # Parse exclude/include lists + exclude_accounts = ( + [acc.strip() for acc in args.exclude.split(",")] if args.exclude else [] + ) + include_accounts = ( + [acc.strip() for acc in args.include.split(",")] if args.include else [] + ) + + print("Fetching accounts from AWS Organizations...") + if args.profile: + print(f"Using AWS profile: {args.profile}") + + # Get accounts from Organizations + accounts = get_org_accounts(profile=args.profile, region=args.region) + + if not accounts: + print("No active accounts found in organization.") + return + + print(f"Found {len(accounts)} active accounts in organization") + + # Generate YAML configuration + providers = generate_yaml_config( + accounts=accounts, + role_name=args.role_name, + external_id=args.external_id, + session_name=args.session_name, + duration_seconds=args.duration_seconds, + alias_format=args.alias_format, + exclude_accounts=exclude_accounts, + include_accounts=include_accounts, + ) + + if not providers: + print("No providers generated after applying filters.") + return + + print(f"Generated configuration for {len(providers)} accounts") + + # Output YAML + yaml_content = yaml.dump( + providers, default_flow_style=False, sort_keys=False, allow_unicode=True + ) + + if args.dry_run: + print("\n--- Generated YAML Configuration ---\n") + print(yaml_content) + else: + with open(args.output, "w", encoding="utf-8") as f: + f.write(yaml_content) + print(f"\nConfiguration written to: {args.output}") + print("\nNext steps:") + print(f" 1. Review the generated file: cat {args.output} | head -n 20") + print( + f" 2. Run bulk provisioning: python prowler_bulk_provisioning.py {args.output}" + ) + + +if __name__ == "__main__": + main() diff --git a/util/prowler-bulk-provisioning/examples/aws-org-example.yaml b/util/prowler-bulk-provisioning/examples/aws-org-example.yaml new file mode 100644 index 0000000000..4224b0d244 --- /dev/null +++ b/util/prowler-bulk-provisioning/examples/aws-org-example.yaml @@ -0,0 +1,49 @@ +# Example AWS Organizations Output +# +# This is an example of what aws_org_generator.py produces when run against +# an AWS Organization. This file can be directly used with prowler_bulk_provisioning.py +# +# Generated with: +# python aws_org_generator.py -o aws-org-accounts.yaml \ +# --role-name ProwlerRole \ +# --external-id prowler-ext-id-12345 + +- provider: aws + uid: '111111111111' + alias: Production-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::111111111111:role/ProwlerRole + external_id: prowler-ext-id-12345 + +- provider: aws + uid: '222222222222' + alias: Development-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::222222222222:role/ProwlerRole + external_id: prowler-ext-id-12345 + +- provider: aws + uid: '333333333333' + alias: Staging-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::333333333333:role/ProwlerRole + external_id: prowler-ext-id-12345 + +- provider: aws + uid: '444444444444' + alias: Security-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::444444444444:role/ProwlerRole + external_id: prowler-ext-id-12345 + +- provider: aws + uid: '555555555555' + alias: Logging-Account + auth_method: role + credentials: + role_arn: arn:aws:iam::555555555555:role/ProwlerRole + external_id: prowler-ext-id-12345 diff --git a/util/prowler-bulk-provisioning/nuke_providers.py b/util/prowler-bulk-provisioning/nuke_providers.py index 348855611f..9759ac4626 100755 --- a/util/prowler-bulk-provisioning/nuke_providers.py +++ b/util/prowler-bulk-provisioning/nuke_providers.py @@ -7,7 +7,7 @@ Use with extreme caution. There is no undo. Environment: PROWLER_API_BASE (default: https://api.prowler.com/api/v1) - PROWLER_API_TOKEN (required unless --token is provided) + PROWLER_API_KEY (required unless --api-key is provided) Usage: python nuke_providers.py --confirm @@ -39,12 +39,14 @@ import requests # ----------------------------- CLI / Utils --------------------------------- # -def env_or_arg(token_arg: Optional[str]) -> str: - """Get API token from argument or environment variable.""" - token = token_arg or os.getenv("PROWLER_API_TOKEN") - if not token: - sys.exit("Missing API token. Set --token or PROWLER_API_TOKEN.") - return token +def env_or_arg(api_key_arg: Optional[str]) -> str: + """Get API key from argument or environment variable.""" + api_key = api_key_arg or os.getenv("PROWLER_API_KEY") + if not api_key: + sys.exit( + "Missing API key. Set --api-key or PROWLER_API_KEY environment variable." + ) + return api_key def normalize_base_url(url: str) -> str: @@ -63,14 +65,14 @@ class ApiClient: """HTTP client for Prowler API.""" base_url: str - token: str + api_key: str verify_ssl: bool = True timeout: int = 60 def _headers(self) -> Dict[str, str]: """Generate HTTP headers for API requests.""" return { - "Authorization": f"Bearer {self.token}", + "Authorization": f"Api-Key {self.api_key}", "Content-Type": "application/vnd.api+json", "Accept": "application/vnd.api+json", } @@ -266,7 +268,9 @@ def main(): help="API base URL (default: env PROWLER_API_BASE or Prowler Cloud SaaS)", ) parser.add_argument( - "--token", default=None, help="Bearer token (default: PROWLER_API_TOKEN)" + "--api-key", + default=None, + help="Prowler API key (default: PROWLER_API_KEY env variable)", ) parser.add_argument( "--filter-provider", @@ -307,12 +311,12 @@ def main(): args = parser.parse_args() - token = env_or_arg(args.token) + api_key = env_or_arg(args.api_key) base_url = normalize_base_url(args.base_url) client = ApiClient( base_url=base_url, - token=token, + api_key=api_key, verify_ssl=not args.insecure, timeout=args.timeout, ) diff --git a/util/prowler-bulk-provisioning/prowler_bulk_provisioning.py b/util/prowler-bulk-provisioning/prowler_bulk_provisioning.py index e7ebdaac3b..d5564515e4 100755 --- a/util/prowler-bulk-provisioning/prowler_bulk_provisioning.py +++ b/util/prowler-bulk-provisioning/prowler_bulk_provisioning.py @@ -132,12 +132,14 @@ def load_items(path: Path) -> List[Dict[str, Any]]: sys.exit(f"Unsupported input file type: {ext}") -def env_or_arg(token_arg: Optional[str]) -> str: - """Get API token from argument or environment variable.""" - token = token_arg or os.getenv("PROWLER_API_TOKEN") - if not token: - sys.exit("Missing API token. Set --token or PROWLER_API_TOKEN.") - return token +def env_or_arg(api_key_arg: Optional[str]) -> str: + """Get API key from argument or environment variable.""" + api_key = api_key_arg or os.getenv("PROWLER_API_KEY") + if not api_key: + sys.exit( + "Missing API key. Set --api-key or PROWLER_API_KEY environment variable." + ) + return api_key def normalize_base_url(url: str) -> str: @@ -395,14 +397,14 @@ class ApiClient: """HTTP client for Prowler API.""" base_url: str - token: str + api_key: str verify_ssl: bool = True timeout: int = 60 def _headers(self) -> Dict[str, str]: """Generate HTTP headers for API requests.""" return { - "Authorization": f"Bearer {self.token}", + "Authorization": f"Api-Key {self.api_key}", "Content-Type": "application/vnd.api+json", "Accept": "application/vnd.api+json", } @@ -564,7 +566,9 @@ def main(): help="API base URL (default: env PROWLER_API_BASE or Prowler Cloud SaaS).", ) parser.add_argument( - "--token", default=None, help="Bearer token (default: PROWLER_API_TOKEN)." + "--api-key", + default=None, + help="Prowler API key (default: PROWLER_API_KEY env variable).", ) parser.add_argument( "--providers-endpoint", @@ -600,7 +604,7 @@ def main(): ) args = parser.parse_args() - token = env_or_arg(args.token) + api_key = env_or_arg(args.api_key) base_url = normalize_base_url(args.base_url) items = load_items(Path(args.input_file)) @@ -610,7 +614,7 @@ def main(): client = ApiClient( base_url=base_url, - token=token, + api_key=api_key, verify_ssl=not args.insecure, timeout=args.timeout, ) diff --git a/util/prowler-bulk-provisioning/requirements-aws-org.txt b/util/prowler-bulk-provisioning/requirements-aws-org.txt new file mode 100644 index 0000000000..4fb57013d7 --- /dev/null +++ b/util/prowler-bulk-provisioning/requirements-aws-org.txt @@ -0,0 +1,11 @@ +# AWS Organizations Generator Dependencies +# +# This extends the base requirements.txt for the AWS Organizations generator + +# Include base dependencies +-r requirements.txt + +# AWS SDK for Python +boto3>=1.26.0 + +# Note: PyYAML is already included in requirements.txt From c0396e97bfd78212ba720d9332a24e0e9650289e Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:09:15 +0200 Subject: [PATCH 49/57] feat(docs): add new provider e2e guide (#8430) Co-authored-by: HugoPBrito Co-authored-by: Sergio Garcia --- docs/developer-guide/provider.mdx | 3472 ++++++++++++++++++++++++++- docs/developer-guide/services.mdx | 304 ++- docs/img/provider-decision-tree.png | Bin 0 -> 72934 bytes prowler/CHANGELOG.md | 1 + 4 files changed, 3678 insertions(+), 99 deletions(-) create mode 100644 docs/img/provider-decision-tree.png diff --git a/docs/developer-guide/provider.mdx b/docs/developer-guide/provider.mdx index e0b7c62284..85dafc6300 100644 --- a/docs/developer-guide/provider.mdx +++ b/docs/developer-guide/provider.mdx @@ -17,160 +17,3442 @@ A provider is any platform or service that offers resources, data, or functional For providers supported by Prowler, refer to [Prowler Hub](https://hub.prowler.com/). -There are some custom providers added by the community, like [NHN Cloud](https://www.nhncloud.com/), that are not maintained by the Prowler team, but can be used in the Prowler CLI. They can be checked directly at the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). - + There are some custom providers added by the community, like [NHN Cloud](https://www.nhncloud.com/), that are not maintained by the Prowler team, but can be used in the Prowler CLI. The main purpose of this documentation is to guide you through creating a new provider and integrating it not only in the CLI, but also in the API and UI. Non official providers can be checked directly at the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). -## Adding a New Provider +--- -To integrate an unsupported Prowler provider and implement its security checks, create a dedicated folder for all related files (e.g., services, checks)." +## Provider Types in Prowler -This folder must be placed within [`prowler/providers//`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). +Prowler supports several types of providers, each with its own implementation pattern and use case. Understanding these differences is key to designing your provider correctly. -Within this folder the following folders are also to be created: +### Classifying your Provider -- `lib` – Stores additional utility functions and core files required by every provider. The following files and subfolders are commonly found in every provider's `lib` folder: +Before implementing a new provider, you need to determine which type it belongs to. This classification will guide your implementation approach and help you choose the right patterns and libraries. - - `service/service.py` – Provides a generic service class to be inherited by all services. - - `arguments/arguments.py` – Handles provider-specific argument parsing. - - `mutelist/mutelist.py` – Manages the mutelist functionality for the provider. +#### Decision Criteria -- `services` – Stores all [services](/developer-guide/services) that the provider offers and want to be audited by [Prowler checks](/developer-guide/checks). +Once you have decided the provider you want or need to add to Prowler, the next step is to study how to retrieve data from it. Based on that, the provider will fall into one of the following types: SDK, API or Tool/Wrapper (maybe in the future there will be new types but for now this are the only ones). -- `__init__.py` (empty) – Ensures Python recognizes this folder as a package. +**Choose SDK Provider if:** -- `_provider.py` – Defines authentication logic, configurations, and other provider-specific data. +- The target platform/service has an **official Python SDK** available +- The target platform/service has a **non-official Python SDK** available but it's been updated and maintained +- You need to support **multiple authentication methods** (profiles, service principals, IAM roles, etc.) +- The SDK provides **built-in session management**, retry logic, and error handling +- You want to leverage **SDK-specific features** like credential chaining, role assumption, etc +- The platform is a **major cloud provider** (AWS, Azure, GCP, etc.) or has mature SDK support -- `models.py` – Contains necessary models for the new provider. +**Choose API Provider if:** -By adhering to this structure, Prowler can effectively support services and security checks for additional providers. +- The target platform has a **REST API** but **no official Python SDK** +- The target platform has a **non-official Python SDK** available but it's not updated and maintained +- You need to implement **custom authentication flows** (OAuth, token-based, etc.) +- The platform is a **custom or community service** without official SDK support +- You want to use **standard HTTP libraries** like `requests` for API calls +- The platform exposes **well-documented REST endpoints** but lacks SDK tooling - -If your new provider requires a Python library (such as an official SDK or API client) to connect to its services, make sure to add it as a dependency in the `pyproject.toml` file. This ensures that all contributors and users have the necessary packages installed when working with your provider. +**Choose Tool/Wrapper Provider if:** - -## Provider Structure in Prowler +- You're integrating a **third-party security tool** or library +- The tool provides **scanning capabilities** that need to be adapted to Prowler's interface +- You don't need **authentication or session management** (the tool handles this) +- You need to **map tool arguments** and **convert outputs** to Prowler's format -Prowler's provider architecture is designed to facilitate security audits through a generic service tailored to each provider. This is accomplished by passing the necessary parameters to the constructor, which initializes all required session values. +**Special Case - Hybrid Providers:** -### Base Class +- Some providers may **combine multiple approaches** (e.g., SDK + Tool wrapper, SDK + API, etc.) +- Example: M365 uses **msgraph SDK** for authentication and some checks, and **PowerShell wrapper** for other checks that the SDK doesn't support +- These require **custom implementation patterns** that blend different provider types -All Prowler providers inherit from the same base class located in [`prowler/providers/common/provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py). It is an [abstract base class](https://docs.python.org/3/library/abc.html) that defines the interface for all provider classes. +#### Classification Examples -### Provider Class +| Provider | Type | Reasoning | +| ----------- | ------ | ----------------------------------------------------------------- | +| AWS | SDK | Official boto3 SDK, multiple auth methods, mature ecosystem | +| Azure | SDK | Official azure-identity SDK, service principals, managed identity | +| GCP | SDK | Official google-auth SDK, service accounts, ADC support | +| Kubernetes | SDK | Official kubernetes SDK, service accounts, ADC support | +| NHN Cloud | API | Custom REST API, no official SDK, community provider | +| MongoDB Atlas| API | Custom REST API, no official SDK | +| IAC | Tool | Third-party security tool that uses trivy, no auth needed, output conversion| +| M365 | Hybrid | Combines msgraph SDK for auth + PowerShell wrapper for operations | +| GitHub | Hybrid | Non-Official PyGithub SDK but it's been updated and maintained + Official graphql API requests| -#### Provider Implementation Guidance +#### Questions to Ask Yourself -Given the complexity and variability of providers, use existing provider implementations as templates when developing new integrations. +**1. Does the platform have an official Python SDK?** -- [AWS](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py) -- [GCP](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py) -- [Azure](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py) -- [Kubernetes](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py) -- [Microsoft365](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/microsoft365/microsoft365_provider.py) -- [GitHub](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/github_provider.py) -- [MongoDB Atlas](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/mongodbatlas/mongodbatlas_provider.py) +- Yes → Consider SDK Provider +- No → Continue to question 2 -### Basic Provider Implementation: Pseudocode Example +**2. Does the platform have a non-official Python SDK?** -To simplify understanding, the following pseudocode outlines the fundamental structure of a provider, including library imports necessary for authentication. +- Yes → Then if the SDK is updated and maintained, consider SDK Provider, otherwise continue to question 3. +- No → Continue to question 3 -```python title="Provider Example Class" +**3. Is this a third-party security tool or library?** -# Library Imports for Authentication +- Yes → Consider Tool/Wrapper Provider +- No → Continue to question 4 -# When implementing authentication for a provider, import the required libraries. +**4. Does the platform expose a REST API?** -from prowler.config.config import load_and_validate_config_file -from prowler.lib.logger import logger -from prowler.lib.mutelist.mutelist import parse_mutelist_file -from prowler.lib.utils.utils import print_boxes -from prowler.providers.common.models import Audit_Metadata +- Yes → Consider API Provider +- No → You may need a custom approach + +![FlowChart Decision](../img/provider-decision-tree.png) + +#### Implementation Complexity + +- **SDK Providers**: Low complexity. You have mature examples like AWS, Azure, GCP, Kubernetes, etc. that you can leverage to implement your provider. +- **API Providers**: Medium complexity. You need to implement the authentication and session management, and the API calls to the provider. You now have NHN and MongoDB Atlas as example to follow. +- **Tool/Wrapper Providers**: High complexity. You need to implement the argument/output mapping to the provider and handle problems that the tool/wrapper may have. You now have IAC and the PowerShell wrapper as example to follow. +- **Hybrid Providers**: High complexity. You need to "customize" your provider, mixing the other types of providers in order to achieve the desired result. You have M365 (msgraph SDK + PowerShell wrapper) and Github (PyGithub SDK + graphql API requests) as examples. + +### Determining Regional vs Non-Regional Architecture + +After classifying your provider type, the next critical decision is determining whether your provider operates with **regional concepts** or is **global/non-regional**. This decision fundamentally affects how your provider and services are structured and executed. + +#### Regional Providers + +Regional providers operate across multiple geographic locations and require region-specific resource discovery and iteration. + +**Examples:** + +- **AWS**: Has regions like `us-east-1`, `eu-west-1`, `ap-southeast-2` +- **Azure**: Has regions like `East US`, `West Europe`, `Australia East` +- **GCP**: Has regions like `us-central1`, `europe-west1`, `asia-southeast1` + +**Implementation Requirements:** + +- Must implement region discovery and iteration +- Services must be instantiated per region or handle multi-region data +- Checks must execute across all available/specified regions +- Resource ARNs/IDs must include region information +- Region-specific client initialization + +**Execution Pattern:** + +```python +# Regional provider execution pattern +for region in provider.get_regions(): + regional_client = service.get_regional_client(region) + regional_resources = regional_client.discover_resources() + # Process regional resources +``` + +#### Non-Regional (Global) Providers + +Non-regional providers operate globally without geographic partitioning. + +**Examples:** + +- **GitHub**: Repositories, organizations are global concepts +- **M365**: Tenants operate globally across Microsoft datacenters +- **Kubernetes**: Clusters are independent units without regional concepts + +**Implementation Requirements:** + +- Single global client/session +- No region iteration required +- Global resource discovery +- Simpler resource identification (no region in ARNs/IDs) +- Single audit execution + +**Execution Pattern:** + +```python +# Non-regional provider execution pattern +global_client = service.get_client() +global_resources = global_client.discover_resources() +# Process all resources in single iteration +``` + +#### Decision Matrix + +| Aspect | Regional Provider | Non-Regional Provider | +| ------------------------ | ------------------------ | ---------------------- | +| **Client Init** | Per-region clients | Single global client | +| **Resource Discovery** | Iterate through regions | Single discovery call | +| **Resource ARN/ID** | Include region | Global identifier/None | +| **Audit Execution** | Multi-region loops | Single execution | +| **Service Architecture** | Region-aware services | Global services | +| **Performance** | Parallelizable by region | Linear execution | + +#### Region Discovery + +Region discovery is the process of getting the list of regions that are available for the account. This is done by the provider and is stored in the `prowler/providers//lib/regions/_regions.py` file. + +```python +# File: prowler/providers/aws/aws_provider.py +def get_aws_enabled_regions(self, current_session: Session) -> set: + """get_aws_enabled_regions returns a set of enabled AWS regions""" + try: + # EC2 Client to check enabled regions + service = "ec2" + default_region = self.get_default_region(service) + ec2_client = current_session.client(service, region_name=default_region) + + enabled_regions = set() + # With AllRegions=False we only get the enabled regions for the account + for region in ec2_client.describe_regions(AllRegions=False).get("Regions", []): + enabled_regions.add(region.get("RegionName")) + + return enabled_regions + except Exception as error: + logger.error(f"{error.__class__.__name__}: {error}") + return set() +``` + +The function returns a JSON file containing the list of regions for the provider. It is used to retrieve the provider’s regions and to validate the region specified by the user. + +```json +# File: prowler/providers/aws/aws_regions_by_service.json (extract) +{ + "services": { + "ec2": { + "regions": { + "aws": [ + "af-south-1", "ap-east-1", "ap-northeast-1", "ap-northeast-2", + "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", + "ca-central-1", "eu-central-1", "eu-north-1", "eu-south-1", + "eu-west-1", "eu-west-2", "eu-west-3", "me-south-1", + "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2" + ], + "aws-cn": ["cn-north-1", "cn-northwest-1"], + "aws-us-gov": ["us-gov-east-1", "us-gov-west-1"] + } + } + } +} +``` + +### Regional Service Implementation + +For detailed guidance on implementing services for regional services, including code examples, service architecture, and check execution patterns, see the [Regional Service Implementation](./services#regional-service-implementation) section in the Services documentation. + +**Key concepts covered:** + +- Threading and parallel processing across regions +- Service implementation patterns for regional providers +- Cross-region resource attribution and ARN handling +- Best practices for performance and error isolation + +## Step 1: Create the Provider Backend (CLI Integration) + +Once the type of provider and its regional architecture are determined, the next step is to start creating the code of the provider. + +### SDK Providers + +General aspects to consider when implementing a new SDK provider: + +**Definition:** + +- Use the official SDK of the provider to interact with its resources and APIs. +- Examples: AWS (boto3), Azure (azure-identity), GCP (google-auth), Kubernetes (kubernetes), M365 (msal/msgraph), GitHub (PyGithub). + +**Typical Use Cases:** + +- Cloud platforms and services with mature Python SDKs. +- Need to support multiple authentication methods (profiles, service principals, etc). +- Providers that offer comprehensive Python libraries for resource management. + +**Key Characteristics:** + +- Authentication and session management handled by the SDK. +- Arguments: Depends on the provider, but for example we can have `profile`, `region`, `tenant_id`, `client_id`, `client_secret`, etc. +- Outputs: Standardized via SDK models and responses. +- Session objects that can be reused across multiple API calls. +- Built-in retry logic and error handling. + +**Implementation Details:** + +- SDK providers typically use credential objects or session objects provided by the official SDK. +- They often support multiple authentication methods (several types of credentials, configuration files, IAM roles, etc.). +- Session management includes token refresh, connection pooling, and retry mechanisms. +- Resource discovery and enumeration is usually straightforward through SDK methods. + +--- + +### Implementation Guide for SDK Providers + +Now it's time to start creating the code needed to implement the provider. + +#### Step 1: Create the Provider Structure + +**Explanation:** +SDK providers require a specific folder structure to organize authentication, configuration, and service management. This structure follows Prowler's conventions and ensures proper integration with the CLI and API. + +**Required Structure:** + +``` +prowler/providers// +├── __init__.py +├── _provider.py +├── models.py +├── exceptions/ +│ ├── __init__.py +│ └── exceptions.py +├── services/ +│ ├── service_name1/ +│ └── service_name2/ +└── lib/ + ├── __init__.py + ├── arguments/ + │ ├── __init__.py + │ └── arguments.py + ├── mutelist/ + │ ├── __init__.py + │ └── mutelist.py + ├── regions/ + │ ├── __init__.py + │ └── _regions.py + └── service/ + ├── __init__.py + └── service.py +``` + +**Key Components:** + +- **`_provider.py`**: Main provider class with authentication and session management +- **`models.py`**: Data structures for identity, session, and provider-specific information +- **`exceptions/`**: Custom exception classes for error handling +- **`services/`**: Folder that contains all the provider services, how to make a new service is explained in another section. +- **`lib/arguments/`**: CLI argument validation and parsing +- **`lib/mutelist/`**: Resource exclusion and muting functionality +- **`lib/regions/`**: Region management and validation. If the provider is NOT regional, this folder will not be created. +- **`lib/service/`**: Base service class for provider-specific services + +#### Step 2: Implement the Provider Class + +**Explanation:** +The provider class is the core component that handles authentication, session management, and identity information. It inherits from Prowler's base Provider class and implements SDK-specific authentication flows. All providers must share, as far as possible, common patterns for session setup, identity management, and credential validation. + +Nevertheless, you may encounter changes and must adapt the implementation logic accordingly. A basic example of a common provider implementation is the following: + +**File:** `prowler/providers//_provider.py` + +```python +import os +from typing import Optional, Union from prowler.providers.common.provider import Provider -from prowler.providers..models import ( - # All provider models needed. - ProviderSessionModel, - ProviderIdentityModel, - ProviderOutputOptionsModel -) +from prowler.providers.common.models import Audit_Metadata, Connection +from prowler.config.config import load_and_validate_config_file, get_default_mute_file_path +from prowler.lib.logger import logger +from prowler.lib.utils.utils import print_boxes -class NewProvider(Provider): - # All properties from the class, some of which are properties in the base class. - _type: str = "" - _session: - _identity: +# Import your SDK and all the needed libraries for the provider. +import your_sdk_library +from your_sdk_library.auth_methods import ClientSecretCredential, ProfileCredential, DefaultCredential + +# Import the needed exceptions, mutelist and models for the provider. +from prowler.providers..exceptions.exceptions import Exceptions +from prowler.providers..mutelist.mutelist import Mutelist +from prowler.providers..models import NeededModels + +class YourProvider(Provider): + """ + YourProvider class is the main class for the Your Provider. + + This class is responsible for initializing the provider, setting up the session, + validating credentials, and managing identity information. + + Attributes: + _type (str): The provider type. + _session (YourSDKSession): The provider session. + _identity (YourProviderIdentityInfo): The provider identity information. + _audit_config (dict): The audit configuration. + _mutelist (YourProviderMutelist): The provider mutelist. + audit_metadata (Audit_Metadata): The audit metadata. + """ + + _type: str = "your_provider" + _session: your_sdk_library.Session + _identity: YourProviderIdentityInfo _audit_config: dict - _output_options: ProviderOutputOptionsModel - _mutelist: dict + _mutelist: YourProviderMutelist audit_metadata: Audit_Metadata - def __init__(self, arguments): + def __init__( + self, + # Authentication parameters + client_id: str = None, + client_secret: str = None, + tenant_id: str = None, + # Configuration + config_path: str = None, + config_content: dict = None, + mutelist_path: str = None, + mutelist_content: dict = None, + # Additional provider-specific parameters + region: str = None, + profile: str = None, + ): """ - Initializes the NewProvider instance. + Initializes the YourProvider instance. + Args: - arguments (dict): A dictionary containing configuration arguments. + client_id: The client ID for authentication + client_secret: The client secret for authentication + tenant_id: The tenant ID for authentication + config_path: Path to the configuration file + config_content: Configuration content as dictionary + mutelist_path: Path to the mutelist file + mutelist_content: Mutelist content as dictionary + region: The region to use + profile: The profile to use + + Raises: + YourProviderSetUpSessionError: If session setup fails + YourProviderInvalidCredentialsError: If credentials are invalid """ - logger.info("Setting provider ...") + logger.info("Initializing YourProvider ...") - # Initializing the Provider Session - - # Steps: - - # - Retrieve Account Information - # - Extract relevant account identifiers (subscriptions, projects, or other service references) from the provided arguments. - - # Establish a Session - - # Use the method enforced by the parent class to set up the session: - self._session = self.setup_session(credentials_file) - - # Define Provider Identity - # Assign the identity class, typically provided by the Python provider library: - self._identity = () - - # Configure the Provider - # Set the provider-specific configuration. - self._audit_config = load_and_validate_config_file( - self._type, arguments.config_file + # Setup session using SDK + self._session = self.setup_session( + client_id, client_secret, tenant_id, region, profile ) - # All the enforced properties by the parent class. + # Get identity information + self._identity = self.setup_identity(self._session) + + # Load configuration + if config_content: + self._audit_config = config_content + else: + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file(self._type, config_path) + + # Setup mutelist + if mutelist_content: + self._mutelist = YourProviderMutelist(mutelist_content=mutelist_content) + else: + if not mutelist_path: + mutelist_path = get_default_mute_file_path(self._type) + self._mutelist = YourProviderMutelist(mutelist_path=mutelist_path) + + Provider.set_global_provider(self) + + @staticmethod + def setup_session( + client_id: str = None, + client_secret: str = None, + tenant_id: str = None, + region: str = None, + profile: str = None, + ) -> your_sdk_library.Session: + """ + Sets up the provider session using the provided credentials. + + This method handles the authentication flow and creates a session object + that can be used to interact with the provider's services. + + Args: + client_id: The client ID for authentication + client_secret: The client secret for authentication + tenant_id: The tenant ID for authentication + region: The region to use + profile: The profile to use + + Returns: + YourSDKSession: The authenticated session object + + Raises: + YourProviderSetUpSessionError: If session setup fails + """ + try: + logger.debug("Creating session ...") + + # Determine authentication method based on provided parameters + if client_id and client_secret and tenant_id: + # Use client credentials authentication + credentials = your_sdk_library.ClientSecretCredential( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret + ) + auth_method = "Client Credentials" + elif profile: + # Use profile-based authentication + credentials = your_sdk_library.ProfileCredential(profile=profile) + auth_method = "Profile" + else: + # Use default authentication (environment variables, etc.) + credentials = your_sdk_library.DefaultCredential() + auth_method = "Default" + + # Create session with credentials + session = your_sdk_library.Session( + credentials=credentials, + region=region + ) + + logger.debug(f"Session created using {auth_method} authentication") + return session + + except Exception as error: + logger.critical(f"Failed to setup session: {error}") + raise YourProviderSetUpSessionError( + original_exception=error, + file=os.path.basename(__file__), + ) + + def setup_identity(self, session: your_sdk_library.Session) -> YourProviderIdentityInfo: + """ + Gets identity information from the provider session. + + This method retrieves account information, user details, and other + identity-related data from the provider. + + Args: + session: The authenticated session object + + Returns: + YourProviderIdentityInfo: The identity information + + Raises: + YourProviderSetUpIdentityError: If identity setup fails + """ + try: + # Use SDK to get account/identity information + identity_info = session.get_identity() + + return YourProviderIdentityInfo( + account_id=identity_info.account_id, + account_name=identity_info.account_name, + region=identity_info.region, + user_id=identity_info.user_id, + # Add other identity fields as needed + ) + except Exception as e: + logger.error(f"Failed to get identity information: {e}") + raise YourProviderSetUpIdentityError( + original_exception=e, + file=os.path.basename(__file__), + ) + @property def identity(self): + """Returns the provider identity information.""" return self._identity @property def session(self): + """Returns the provider session object.""" return self._session @property def type(self): + """Returns the provider type.""" return self._type @property def audit_config(self): + """Returns the audit configuration.""" return self._audit_config @property - def output_options(self): - return self._output_options + def mutelist(self): + """Returns the provider mutelist.""" + return self._mutelist - def setup_session(self, ): + def print_credentials(self): """ - Sets up the Provider session. + Display account information with color formatting. + + This method prints the provider credentials and account information + in a formatted way using colorama for better readability. + """ + from colorama import Fore, Style + from prowler.lib.utils.utils import print_boxes + + report_lines = [ + f" Account ID: {Fore.YELLOW}{self._identity.account_id}{Style.RESET_ALL}", + f" Account Name: {Fore.YELLOW}{self._identity.account_name}{Style.RESET_ALL}", + f" Region: {Fore.YELLOW}{self._identity.region}{Style.RESET_ALL}", + f" User ID: {Fore.YELLOW}{self._identity.user_id}{Style.RESET_ALL}", + ] + report_title = f"{Style.BRIGHT}Using the {self._type.upper()} credentials below:{Style.RESET_ALL}" + print_boxes(report_lines, report_title) + + @staticmethod + def test_connection( + client_id: str = None, + client_secret: str = None, + tenant_id: str = None, + region: str = None, + profile: str = None, + raise_on_exception: bool = True, + provider_id: str = None, + ) -> Connection: + """ + Test connection to the provider. + + This method validates the provided credentials and tests the connection + to the provider's services. Args: - Can include all necessary arguments to set up the session + client_id: The client ID for authentication + client_secret: The client secret for authentication + tenant_id: The tenant ID for authentication + region: The region to test + profile: The profile to use + raise_on_exception: Whether to raise exceptions or return Connection object + provider_id: The provider ID to validate against Returns: - Credentials necessary to communicate with the provider. - """ - pass + Connection: Connection test result - """ - This method is enforced by parent class and is used to print all relevant - information during the prowler execution as a header of execution. - Displaying Account Information with Color Formatting. In Prowler, Account IDs, usernames, and other identifiers are typically displayed using color formatting provided by the colorama module (Fore). - """ - def print_credentials(self): - pass + Raises: + YourProviderSetUpSessionError: If session setup fails + YourProviderInvalidCredentialsError: If credentials are invalid + """ + try: + # Create temporary session for testing + test_session = YourProvider.setup_session( + client_id, client_secret, tenant_id, region, profile + ) + + # Test the connection by getting identity + identity = YourProvider.setup_identity(test_session) + + # Validate provider ID if provided + if provider_id and identity.account_id != provider_id: + raise YourProviderInvalidProviderIdError( + file=os.path.basename(__file__), + ) + + return Connection( + status=True, + message=f"Successfully connected to {provider_id or 'provider'}", + error=None, + ) + except Exception as e: + if raise_on_exception: + raise e + return Connection( + status=False, + message="Failed to connect", + error=str(e), + ) + + def get_regions(self) -> set: + """ + Get available regions for the provider. + + Returns: + set: Set of available region names + """ + # Implementation depends on your provider + # Example for cloud providers that support regions + return {"region1", "region2", "region3"} + + def get_services(self) -> list: + """ + Get available services for the provider. + + Returns: + list: List of available service names + """ + # Implementation depends on your provider + return ["service1", "service2", "service3"] ``` + +#### Step 3: Create Models + +**Explanation:** +Models define the data structures used by your provider. They include identity information, session details, and provider-specific configurations. These models ensure type safety and consistent data handling across the provider. + +**File:** `prowler/providers//models.py` + +```python +# Import the needed generic libraries for the provider. +from pydantic import BaseModel +from dataclasses import dataclass +from typing import Optional, List + +# Import the needed Prowler libraries for the provider. +from prowler.providers.common.models import ProviderOutputOptions +from prowler.config.config import output_file_timestamp + +class YourProviderIdentityInfo: + """ + Identity information for the provider. + + This class holds all the identity-related information retrieved + from the provider, including account details and user information. + """ + account_id: str + account_name: str + region: str + user_id: str + # Add other identity fields as needed + +class YourProviderSession: + """ + Session object that contains the credentials and authentication details for the provider. + + This class holds the actual credentials and authentication information needed + to establish a connection with the provider's services. + """ + # Authentication credentials + access_key: str + secret_key: str + # Or for other providers: + # client_id: str + # client_secret: str + # tenant_id: str + + # Connection details + region: str +``` + +#### Step 4: Implement Arguments + +**Explanation:** +Argument validation ensures that the provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users. The validation should check for required parameters and validate their format. + +**File:** `prowler/providers//lib/arguments/arguments.py` + +```python +def init_parser(self): + """Init the Provider CLI parser""" + _parser = self.subparsers.add_parser( + "", parents=[self.common_providers_parser], help=" Provider" + ) + # Authentication Modes + _auth_subparser = _parser.add_argument_group("Authentication Modes") + _auth_modes_group = _auth_subparser.add_mutually_exclusive_group() + _auth_modes_group.add_argument( + "--credentials-file", + nargs="?", + metavar="FILE_PATH", + help="Authenticate using a Service Account Application Credentials JSON file", + ) + _auth_modes_group.add_argument( + "--impersonate-service-account", + nargs="?", + metavar="SERVICE_ACCOUNT", + help="Impersonate a Service Account", + ) + _parser.add_argument( + "--your-provider-region", + help="Your Provider Region", + type=str, + ) + _parser.add_argument( + "--env-auth", + action="store_true", + help="Use User and Password environment variables authentication to log in against ", + ) + # More arguments for the provider. +``` + +#### Step 5: Implement Mutelist + +**Explanation:** +The mutelist functionality allows users to exclude specific resources or checks from the audit. This is useful for handling false positives or excluding resources that are intentionally configured differently. + +**File:** `prowler/providers//lib/mutelist/mutelist.py` + +```python +from prowler.lib.mutelist.mutelist import Mutelist +from prowler.lib.check.models import CheckReportYourProvider + +class YourProviderMutelist(Mutelist): + """ + Mutelist implementation for YourProvider. + + This class handles the muting functionality for the provider, + allowing users to exclude specific checks or resources from audits. + """ + + def is_finding_muted(self, finding: CheckReportYourProvider) -> bool: + """ + Check if a specific finding is muted. + + Args: + finding: The finding to check + """ + return self.is_muted(finding.check_id, finding.resource_id) +``` + +#### Step 6: Implement Regions + +**Explanation:** +Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality. + + +Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does. + + +**File:** `prowler/providers//lib/regions/_regions.py` + +```python +from typing import List, Set + +def get_regions() -> List[str]: + """ + Get list of available regions for the provider. + + Returns: + List[str]: List of available region names + """ + return [ + "region1", + "region2", + "region3", + # ... other regions + ] + +def validate_region(region: str) -> bool: + """ + Validate if a region is supported. + + Args: + region: The region to validate + + Returns: + bool: True if the region is valid, False otherwise + """ + return region in get_regions() + +def get_default_region() -> str: + """ + Get the default region for the provider. + + Returns: + str: The default region name + """ + return "region1" + +def get_global_region() -> str: + """ + Get the global region for the provider. + + Returns: + str: The global region name + """ + return "global" +``` + +#### Step 7: Create Custom Exceptions + +**Explanation:** +Custom exceptions are needed to be able to handle the errors in a more specific way. Prowler uses a structured exception system with error codes, messages, and remediation steps. + +**File:** `prowler/providers//exceptions.py` + +```python +from prowler.exceptions.exceptions import ProwlerException + + +# Exceptions codes from 7000 to 7999 are reserved for YourProvider exceptions (Numbers as example) +class YourProviderBaseException(ProwlerException): + """Base class for YourProvider Errors.""" + + YOUR_PROVIDER_ERROR_CODES = { + (7001, "YourProviderCredentialsError"): { + "message": "Error loading credentials for YourProvider", + "remediation": "Check the credentials and ensure they are properly set up. API_KEY and API_SECRET are required.", + }, + (7002, "YourProviderAuthenticationError"): { + "message": "Authentication failed with YourProvider", + "remediation": "Check the API credentials and ensure they are valid and have proper permissions.", + }, + (7003, "YourProviderInvalidRegionError"): { + "message": "Invalid region provided for YourProvider", + "remediation": "Check the region and ensure it is a valid region for YourProvider.", + }, + (7004, "YourProviderSetUpSessionError"): { + "message": "Error setting up session", + "remediation": "Check the session setup and ensure it is properly configured.", + }, + (7005, "YourProviderInvalidProviderIdError"): { + "message": "Provider does not match with the expected account_id", + "remediation": "Check the provider and ensure it matches the expected account_id.", + }, + } + + def __init__(self, code, file=None, original_exception=None, message=None): + provider = "YourProvider" + error_info = self.YOUR_PROVIDER_ERROR_CODES.get((code, self.__class__.__name__)) + if message: + error_info["message"] = message + super().__init__( + code=code, + source=provider, + file=file, + original_exception=original_exception, + error_info=error_info, + ) + + +class YourProviderCredentialsError(YourProviderBaseException): + """Base class for YourProvider credentials errors.""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 7001, file=file, original_exception=original_exception, message=message + ) + + +class YourProviderAuthenticationError(YourProviderCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 7002, file=file, original_exception=original_exception, message=message + ) + + +class YourProviderInvalidRegionError(YourProviderBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 7003, file=file, original_exception=original_exception, message=message + ) + + +class YourProviderSetUpSessionError(YourProviderCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 7004, file=file, original_exception=original_exception, message=message + ) + + +class YourProviderInvalidProviderIdError(YourProviderBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 7005, file=file, original_exception=original_exception, message=message + ) +``` + +#### Step 8: Implement Service Base Class + +**Explanation:** +The service base class defines a common interface for all services in your provider, since they will inherit from it. It defines the client to make requests to, the audit configuration and the fixer configuration. + +**File:** `prowler/providers//lib/service/service.py` + +```python +from prowler.providers.._provider import Provider + +class YourProviderService(BaseService): + """ + Base service class for YourProvider services. + + This class provides common functionality for all services + within the provider, including session management and error handling. + """ + + def __init__(self, provider: Provider): + """ + Initialize the service. + + Args: + provider: The provider instance + """ + self.client = provider.session.get_client(self.service_name) + self.audit_config = provider.audit_config + self.fixer_config = provider.fixer_config +``` + +#### Step 9: Register in CLI + +**Explanation:** +Add your provider to the available providers in the CLI. + +**File:** `prowler/lib/cli/parser.py` + +```python +class ProwlerArgumentParser: + # Set the default parser + def __init__(self): + # CLI Arguments + self.parser = argparse.ArgumentParser( + prog="prowler", + formatter_class=RawTextHelpFormatter, + usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,dashboard,iac,your_provider} ...", + epilog=""" +Available Cloud Providers: + {aws,azure,gcp,kubernetes,m365,github,iac,nhn,your_provider} + aws AWS Provider + azure Azure Provider + gcp GCP Provider + kubernetes Kubernetes Provider + m365 Microsoft 365 Provider + github GitHub Provider + iac IaC Provider (Preview) + nhn NHN Provider (Unofficial) + your_provider Your Provider + +Available components: + dashboard Local dashboard + +To see the different available options on a specific component, run: + prowler {provider|dashboard} -h|--help + +Detailed documentation at https://docs.prowler.com +""", +``` + +#### Step 10: Register in Main + +**Explanation:** +Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation. + +**File:** `prowler/__main__.py` + +```python +# In the prowler setup output options section + if provider == "aws": + output_options = AWSOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "azure": + output_options = AzureOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "gcp": + output_options = GCPOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "kubernetes": + output_options = KubernetesOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "github": + output_options = GithubOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "m365": + output_options = M365OutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "nhn": + output_options = NHNOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + elif provider == "iac": + output_options = IACOutputOptions( + args, bulk_checks_metadata + ) + elif provider == "your_provider": + output_options = YourProviderOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) + + + # Setup Compliance Options + elif provider == "your_provider": + for compliance_name in input_compliance_frameworks: + if compliance_name.startswith("cis_"): + # Generate CIS Finding Object (example of compliance with CIS framework) + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + cis = YourProviderCIS( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(cis) + cis.batch_write_data_to_file() +``` + +#### Step 11: Register in the list of providers + +**Explanation:** +This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization. + +**File:** `prowler/providers/common/provider.py` + +```python +elif "your_provider" in provider_class_name.lower(): + provider_class( + username=arguments.your_provider_username, + password=arguments.your_provider_password, + tenant_id=arguments.your_provider_tenant_id, + config_path=arguments.config_file, + mutelist_path=arguments.mutelist_file, + fixer_config=fixer_config, + ) +``` + +#### Step 12: Add to Config + +**Explanation:** +Configuration registration ensures your provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults. + +**File:** `prowler/config/config.py` + +```python +class Provider(str, Enum): + AWS = "aws" + AZURE = "azure" + GCP = "gcp" + KUBERNETES = "kubernetes" + M365 = "m365" + GITHUB = "github" + YOUR_PROVIDER = "your_provider" # Add your provider here +``` + +In some cases, you may need to create a new configuration file for your provider, for example, the AWS one that is inside `prowler/providers/aws/config.py`. + +#### Step 13: Create Compliance Files + +**Explanation:** +Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional. + +**Folder:** `prowler/compliance//` + +```json +{ + "Framework": "CIS", + "Version": "1.0", + "Provider": "your_provider", + "Description": "Description of the compliance framework", + # The requirements depends on the framework, for example, CIS has a requirements section with the checks and attributes. + "Requirements": [ + { + "Id": "1.1.1", + "Description": "Description of the requirement", + "Checks": ["your_provider_check_1", "your_provider_check_2"], + "Attributes": [] + } + ] +} +``` + +#### Step 14: Add Output Support + +**Explanation:** +Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system. + +**File:** `prowler/lib/outputs/summary_table.py` + +```python +# Add your provider case in the display_summary_table function +elif provider.type == "your_provider": + entity_type = "Your Entity Type" + audited_entities = provider.identity.your_entity_field +``` + +**File:** `prowler/lib/outputs/finding.py` + +```python +# Add your provider case in the fill_common_finding_data function +elif provider.type == "your_provider": + output_data["auth_method"] = f"Your Auth Method: {get_nested_attribute(provider, 'identity.auth_type')}" + output_data["account_uid"] = get_nested_attribute(provider, "identity.account_id") + output_data["account_name"] = get_nested_attribute(provider, "identity.account_name") + output_data["resource_name"] = check_output.resource_name + output_data["resource_uid"] = check_output.resource_id + output_data["region"] = check_output.location # or your location field +``` + +**File:** `prowler/lib/outputs/outputs.py` + +```python +# Add your provider case in the stdout_report function +if finding.check_metadata.Provider == "your_provider": + details = finding.your_location_field # e.g., finding.location, finding.namespace, etc. +``` + +#### Step 15: Generate the HTML Report + +**Explanation:** +The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings. + +**File:** `prowler/lib/outputs/html/html.py` + +```python +@staticmethod +def get_your_provider_assessment_summary(provider: Provider) -> str: + """ + get_your_provider_assessment_summary gets the HTML assessment summary for your provider + + Args: + provider (Provider): the provider object + + Returns: + str: the HTML assessment summary + """ + try: + return f""" +
+
+
+ Your Provider Assessment Summary +
+
    +
  • + Your Entity Type: {provider.identity.your_entity_field} +
  • +
  • + Your Location Field: {provider.identity.your_location_field} +
  • +
+
+
+
+
+
+ Your Provider Credentials +
+
    +
  • + Authentication Method: {provider.auth_method} +
  • +
  • + Identity ID: {provider.identity.identity_id} +
  • +
+
+
""" + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}" + ) + return "" +``` + +#### Step 16: Add the Check Report Model + +**Explanation:** +Add the provider to the generic models, this is needed to be able to use the provider in the generic checks. + +**File:** `prowler/providers/check/models.py` + +```python +@dataclass +class CheckReportYourProvider(CheckReport): + """ + Check report for YourProvider. + """ + resource_name: str + resource_id: str + + def _init_(self, metadata: Dict, resource: Any) -> None: + super()._init_(metadata, resource) + self.resource_name = resource.name + self.resource_id = resource.id +``` + +#### Step 17: Add Dependencies + +**Explanation:** +Dependencies ensure that your provider's required libraries are available when Prowler is installed. This step adds the necessary SDK or API client to Prowler's dependency management. + +**File:** `pyproject.toml` + +```toml +[tool.poetry.dependencies] +python = "^3.9" +# ... other dependencies +your-sdk-library = "^1.0.0" # Add your SDK dependency +``` + +#### Step 18: Create Tests + +**Explanation:** +Testing ensures that your provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover authentication, session management, and provider-specific functionality. + +**Folder:** `tests/providers//` + +```python +import pytest +from prowler.providers.your_provider.your_provider import YourProvider + +class TestYourProvider: + """Test cases for YourProvider.""" + + def test_provider_initialization_with_client_credentials(self): + """Test provider initialization with client credentials.""" + provider = YourProvider( + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id" + ) + assert provider.type == "your_provider" + assert provider.identity is not None + assert provider.session is not None + + def test_provider_initialization_with_profile(self): + """Test provider initialization with profile.""" + provider = YourProvider( + profile="test_profile" + ) + assert provider.type == "your_provider" + assert provider.identity is not None + + def test_connection_test(self): + """Test connection functionality.""" + result = YourProvider.test_connection( + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id" + ) + # Add assertions based on expected behavior + + def test_identity_retrieval(self): + """Test identity information retrieval.""" + provider = YourProvider( + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id" + ) + assert provider.identity.account_id is not None + assert provider.identity.account_name is not None + + def test_argument_validation(self): + """Test argument validation.""" + from prowler.providers.your_provider.lib.arguments.arguments import ( + validate_your_provider_arguments + ) + + # Valid arguments + validate_your_provider_arguments( + client_id="test_client_id", + client_secret="test_client_secret", + tenant_id="test_tenant_id" + ) + + # Invalid arguments + with pytest.raises(ValueError, match="at least one authentication method"): + validate_your_provider_arguments() +``` + +#### Step 19: Update Documentation + +**Explanation:** +Documentation updates ensure that users can find information about your provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information. + +Update the provider documentation to include your new provider in the examples and implementation guidance. + +--- + +### API Providers + +**Definition:** + +- Interact directly with the provider's REST API using HTTP requests (e.g., via `requests`). +- Examples: NHN Cloud. + +**Typical Use Cases:** + +- Providers without an official Python SDK. +- Providers with a non-official Python SDK that is not updated and maintained. +- Providers that expose REST APIs and meet above requirements. + +**Key Characteristics:** + +- Manual management of authentication (tokens, username/password, etc). +- Arguments: Depends on the provider, for example, `username`, `password`, `tenant_id`, etc. +- Outputs: Dicts or custom models based on API responses. +- Custom HTTP session management with headers and authentication. +- Manual handling of pagination, rate limiting, and error responses. + +**Implementation Details:** + +- API providers require manual HTTP request management using libraries like `requests`. +- Authentication typically involves obtaining tokens via login endpoints or OAuth flows. +- Session management includes setting appropriate headers (Authorization, Content-Type, etc.). +- Resource discovery often requires multiple API calls to different endpoints. +- Error handling and retry logic must be implemented manually. + +--- + +### Implementation Guide for API Providers + +#### Step 1: Create the Provider Structure + +**Explanation:** +API providers require the same structure as the SDK providers, the main difference would be that due to the lack of an official Python SDK, some methods could be implemented differently or not implemented at all. + +**Required Structure:** + +``` +prowler/providers// +├── __init__.py +├── _provider.py +├── models.py +├── exceptions/ +│ ├── __init__.py +│ └── exceptions.py +├── services/ +│ ├── service_name1/ +│ └── service_name2/ +└── lib/ + ├── __init__.py + ├── arguments/ + │ ├── __init__.py + │ └── arguments.py + ├── mutelist/ + │ ├── __init__.py + │ └── mutelist.py + ├── regions/ + │ ├── __init__.py + │ └── regions.py + └── service/ + ├── __init__.py + └── service.py +``` + +**Key Components:** + +- **`_provider.py`**: Main provider class with HTTP session management +- **`models.py`**: Data structures for identity and API responses +- **`exceptions/`**: Custom exception classes for API errors +- **`services/`**: Folder that contains all the provider services +- **`lib/arguments/`**: CLI argument validation and parsing +- **`lib/mutelist/`**: Resource exclusion and muting functionality +- **`lib/regions/`**: Region management and validation. If the provider is NOT regional, this folder will not be created. +- **`lib/service/`**: Base service class for provider-specific services + +#### Step 2: Implement the Provider Class + +**Explanation:** +The provider class is the core component that handles HTTP session management, authentication, and identity information. It inherits from Prowler's base Provider class and implements API-specific authentication flows using direct HTTP requests. + +**File:** `prowler/providers//_provider.py` + +```python +import os +from typing import Optional +import requests +from prowler.providers.common.provider import Provider +from prowler.providers.common.models import Audit_Metadata, Connection +from prowler.config.config import load_and_validate_config_file, get_default_mute_file_path +from prowler.lib.logger import logger +from prowler.lib.utils.utils import print_boxes + +# Import the needed exceptions, mutelist and models for the provider. +from prowler.providers..exceptions.exceptions import Exceptions +from prowler.providers..lib.mutelist.mutelist import Mutelist +from prowler.providers..models import NeededModels + +class APIProvider(Provider): + """ + APIProvider class is the main class for the API Provider. + + This class is responsible for initializing the provider, setting up the HTTP session, + validating credentials, and managing identity information through direct API calls. + + Attributes: + _type (str): The provider type. + _session (requests.Session): The HTTP session for API calls. + _identity (APIIdentityInfo): The provider identity information. + _audit_config (dict): The audit configuration. + _mutelist (APIMutelist): The provider mutelist. + audit_metadata (Audit_Metadata): The audit metadata. + """ + + _type: str = "api_provider" + _session: Optional[requests.Session] + _identity: APIIdentityInfo + _audit_config: dict + _mutelist: APIMutelist + audit_metadata: Audit_Metadata + + def __init__( + self, + # Authentication parameters + username: str = None, + password: str = None, + tenant_id: str = None, + # Configuration + config_path: str = None, + config_content: dict = None, + mutelist_path: str = None, + mutelist_content: dict = None, + fixer_config: dict = None, + ): + """ + Initializes the APIProvider instance. + + Args: + username: The API username for authentication + password: The API password for authentication + tenant_id: The tenant ID for authentication + config_path: Path to the configuration file + config_content: Configuration content as dictionary + mutelist_path: Path to the mutelist file + mutelist_content: Mutelist content as dictionary + fixer_config: Fixer configuration dictionary + + Raises: + ValueError: If required authentication parameters are missing + """ + logger.info("Initializing APIProvider ...") + + # 1) Store argument values with environment variable fallback + self._username = username or os.getenv("YOUR_PROVIDER_USERNAME") + self._password = password or os.getenv("YOUR_PROVIDER_PASSWORD") + self._tenant_id = tenant_id or os.getenv("YOUR_PROVIDER_TENANT_ID") + + # Validate required parameters + if not all([self._username, self._password, self._tenant_id]): + raise ValueError("APIProvider requires username, password and tenant_id") + + # 2) Load audit_config, fixer_config, mutelist + self._fixer_config = fixer_config if fixer_config else {} + + if config_content: + self._audit_config = config_content + else: + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file(self._type, config_path) + + if mutelist_content: + self._mutelist = APIMutelist(mutelist_content=mutelist_content) + else: + if not mutelist_path: + mutelist_path = get_default_mute_file_path(self._type) + self._mutelist = APIMutelist(mutelist_path=mutelist_path) + + # 3) Initialize session/token + self._token = None + self._session = None + self.setup_session() + + # 4) Create identity object + self._identity = APIIdentityInfo( + tenant_id=self._tenant_id, + username=self._username, + ) + + Provider.set_global_provider(self) + + @property + def type(self) -> str: + """Returns the type of the provider.""" + return self._type + + @property + def identity(self) -> APIIdentityInfo: + """Returns the provider identity information.""" + return self._identity + + @property + def session(self) -> requests.Session: + """Returns the HTTP session for API calls.""" + return self._session + + @property + def audit_config(self) -> dict: + """Returns the audit configuration.""" + return self._audit_config + + @property + def fixer_config(self) -> dict: + """Returns the fixer configuration.""" + return self._fixer_config + + @property + def mutelist(self) -> APIMutelist: + """Returns the provider mutelist.""" + return self._mutelist + + def print_credentials(self) -> None: + """ + Display account information with color formatting. + + This method prints the provider credentials and account information + in a formatted way using colorama for better readability. + """ + from colorama import Style + + report_lines = [ + f" Username: {self._username}", + f" TenantID: {self._tenant_id}", + ] + report_title = f"{Style.BRIGHT}Using the {self._type.upper()} credentials below:{Style.RESET_ALL}" + print_boxes(report_lines, report_title) + + def setup_session(self) -> None: + """ + Implement API authentication method by calling the provider's authentication endpoint. + + This method performs the authentication flow to obtain an access token + and creates a requests.Session with the appropriate headers for API calls. + """ + # Example for a Keystone-like authentication + url = "https://api.your-provider.com/v2.0/tokens" + data = { + "auth": { + "tenantId": self._tenant_id, + "passwordCredentials": { + "username": self._username, + "password": self._password, + }, + } + } + + try: + response = requests.post(url, json=data, timeout=10) + if response.status_code == 200: + resp_json = response.json() + self._token = resp_json["access"]["token"]["id"] + + # Create session with authentication headers + sess = requests.Session() + sess.headers.update({ + "X-Auth-Token": self._token, + "Content-Type": "application/json" + }) + self._session = sess + logger.info("API token acquired successfully and session is set up.") + else: + logger.critical( + f"Failed to get token. Status: {response.status_code}, Body: {response.text}" + ) + raise ValueError("Failed to get API token") + except Exception as e: + logger.critical(f"[setup_session] Error: {e}") + raise e + + @staticmethod + def test_connection( + username: str, + password: str, + tenant_id: str, + raise_on_exception: bool = True, + ) -> Connection: + """ + Test connection to the API provider by performing: + 1) Authentication token request + 2) (Optional) a small test API call to confirm credentials are valid + + Args: + username: The API username + password: The API password + tenant_id: The tenant ID + raise_on_exception: If True, raise the caught exception; + if False, return Connection(error=exception). + + Returns: + Connection: Connection test result + """ + try: + # 1) Validate arguments + if not username or not password or not tenant_id: + error_msg = "API test_connection error: missing username/password/tenant_id" + logger.error(error_msg) + raise ValueError(error_msg) + + # 2) Request authentication token + token_url = "https://api.your-provider.com/v2.0/tokens" + data = { + "auth": { + "tenantId": tenant_id, + "passwordCredentials": { + "username": username, + "password": password, + }, + } + } + + resp = requests.post(token_url, json=data, timeout=10) + if resp.status_code != 200: + error_msg = f"Failed to get token. Status: {resp.status_code}, Body: {resp.text}" + logger.error(error_msg) + if raise_on_exception: + raise Exception(error_msg) + return Connection(error=Exception(error_msg)) + + # Success + token_json = resp.json() + api_token = token_json["access"]["token"]["id"] + logger.info("API test_connection: Successfully acquired token.") + + # 3) (Optional) Test API call to confirm credentials are valid + test_endpoint = f"https://api.your-provider.com/v2/{tenant_id}/test" + headers = { + "X-Auth-Token": api_token, + "Content-Type": "application/json", + } + + test_resp = requests.get(test_endpoint, headers=headers, timeout=10) + if test_resp.status_code == 200: + logger.info("API test_connection: Test call success. Credentials valid.") + return Connection(is_connected=True) + else: + error_msg = f"Test call failed. Status: {test_resp.status_code}, Body: {test_resp.text}" + logger.error(error_msg) + if raise_on_exception: + raise Exception(error_msg) + return Connection(error=Exception(error_msg)) + + except Exception as e: + logger.critical(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}") + if raise_on_exception: + raise e + return Connection(error=e) + + @staticmethod + def validate_arguments(username: str, password: str, tenant_id: str) -> None: + """ + Ensures that username, password, and tenant_id are not empty. + + Args: + username: The username to validate + password: The password to validate + tenant_id: The tenant ID to validate + + Raises: + ValueError: If any required parameter is missing + """ + if not username or not password or not tenant_id: + raise ValueError("API Provider requires username, password and tenant_id.") +``` + +#### Step 3: Create Models + +**Explanation:** +Models define the data structures used by your API provider. They include identity information and API response structures. These models ensure type safety and consistent data handling across the provider. + +**File:** `prowler/providers//models.py` + +This step is common with SDK providers so you can follow the same pattern as [there](#step-3-create-models). + +#### Step 4: Implement Arguments + +**Explanation:** +Argument validation ensures that the API provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users. + +**File:** `prowler/providers//lib/arguments/arguments.py` + +Arguments depends on the provider and not the type, so the pattern for this step is the same as the [SDK providers](#step-4-implement-arguments). + +#### Step 5: Implement Mutelist + +**Explanation:** +The mutelist functionality allows users to exclude specific resources or checks from the audit. This is useful for handling false positives or excluding resources that are intentionally configured differently. + +**File:** `prowler/providers//lib/mutelist/mutelist.py` + +The implementation of the mutelist is the same as the [SDK providers](#step-5-implement-mutelist). + +#### Step 6: Implement Regions + +**Explanation:** +Region management is essential for cloud providers that operate across multiple geographic locations. This component handles region validation and provides region-specific functionality. + + +Regions are optional, only if the provider has regions, for example Github does not have regions, but AWS does. + + +**File:** `prowler/providers//lib/regions/_regions.py` + +The implementation of the regions is the same as the [SDK providers](#step-6-implement-regions). + +#### Step 7: Create Custom Exceptions + +**Explanation:** +Custom exceptions provide specific error handling for API-related issues, making debugging and error reporting more effective. Prowler uses a structured exception system with error codes, messages, and remediation steps. + +**File:** `prowler/providers//exceptions/exceptions.py` + +```python +from prowler.exceptions.exceptions import ProwlerException + + +# Exceptions codes from 8000 to 8999 are reserved for API Provider exceptions (example numbers) +class APIProviderBaseException(ProwlerException): + """Base class for API Provider Errors.""" + + APIProvider_ERROR_CODES = { + (8000, "APIProviderCredentialsError"): { + "message": "API Provider credentials not found or invalid", + "remediation": "Check the API Provider API credentials and ensure they are properly set.", + }, + (8001, "APIProviderAuthenticationError"): { + "message": "API Provider authentication failed", + "remediation": "Check the API Provider API credentials and ensure they are valid.", + }, + (8002, "APIProviderSessionError"): { + "message": "API Provider session setup failed", + "remediation": "Check the session setup and ensure it is properly configured.", + }, + (8003, "APIProviderIdentityError"): { + "message": "API Provider identity setup failed", + "remediation": "Check credentials and ensure they are properly set up for API Provider.", + }, + (8004, "APIProviderAPIError"): { + "message": "API Provider API call failed", + "remediation": "Check the API request and ensure it is properly formatted.", + }, + (8005, "APIProviderRateLimitError"): { + "message": "API Provider API rate limit exceeded", + "remediation": "Reduce the number of API requests or wait before making more requests.", + }, + } + + def __init__(self, code, file=None, original_exception=None, message=None): + provider = "API Provider" + error_info = self.APIProvider_ERROR_CODES.get((code, self.__class__.__name__)) + if message: + error_info["message"] = message + super().__init__( + code=code, + source=provider, + file=file, + original_exception=original_exception, + error_info=error_info, + ) + + +class APIProviderCredentialsError(APIProviderBaseException): + """Exception for API Provider credentials errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8000, + file=file, + original_exception=original_exception, + message=message, + ) + + +class APIProviderAuthenticationError(APIProviderBaseException): + """Exception for API Provider authentication errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8001, + file=file, + original_exception=original_exception, + message=message, + ) + + +class APIProviderSessionError(APIProviderBaseException): + """Exception for API Provider session setup errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8002, + file=file, + original_exception=original_exception, + message=message, + ) + + +class APIProviderIdentityError(APIProviderBaseException): + """Exception for API Provider identity setup errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8003, + file=file, + original_exception=original_exception, + message=message, + ) + + +class APIProviderAPIError(APIProviderBaseException): + """Exception for API Provider API errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8004, + file=file, + original_exception=original_exception, + message=message, + ) + + +class APIProviderRateLimitError(APIProviderBaseException): + """Exception for API Provider rate limit errors""" + + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + code=8005, + file=file, + original_exception=original_exception, + message=message, + ) +``` + +#### Step 8: Implement Service Base Class + +**Explanation:** +The service base class defines a common interface for all services in your provider, since they will inherit from it. It defines the client to make requests to, the audit configuration and the fixer configuration. + +**File:** `prowler/providers//lib/service/service.py` + +```python +from prowler.providers.._provider import Provider + +class APIProviderService(BaseService): + """ + Base service class for API Provider services. + + This class provides common functionality for all services + within the provider, including session management and error handling. + """ + + def __init__(self, provider: Provider): + """ + Initialize the service. + + Args: + provider: The provider instance + """ + self.client = provider.session.get_client(self.service_name) + self.audit_config = provider.audit_config + self.fixer_config = provider.fixer_config + self.session = provider.session + self.base_url = provider.session.base_url + self.auth = HTTPDigestAuth( + provider.session.public_key, + provider.session.private_key, + ) + self.headers = { + "Authorization": self.auth.encode(), + "Content-Type": "application/json", + } +``` + +#### Step 9: Register in CLI + +**Explanation:** +Add your provider to the available providers in the CLI. + +**File:** `prowler/lib/cli/parser.py` + +This step is the same as the [SDK providers](#step-9-register-in-cli). + +#### Step 10: Register in Main + +**Explanation:** +Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation. + +**File:** `prowler/__main__.py` + +This step is the same as the [SDK providers](#step-10-register-in-main). + +#### Step 11: Register in the list of providers + +**Explanation:** +This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization. + +**File:** `prowler/providers/common/provider.py` + +This step is the same as the [SDK providers](#step-11-register-in-the-list-of-providers). + +#### Step 12: Add to Config + +**Explanation:** +Configuration registration ensures your API provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults. + +**File:** `prowler/config/config.py` + +This step is the same as the [SDK providers](#step-12-add-to-config). + +#### Step 13: Create Compliance Files + +**Explanation:** +Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional. + +**Folder:** `prowler/compliance//` + +This step is the same as the [SDK providers](#step-13-create-compliance-files). + +#### Step 14: Add Output Support + +**Explanation:** +Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system. + +**File:** `prowler/lib/outputs/summary_table.py` + +This step is the same as the [SDK providers](#step-14-add-output-support). + +#### Step 15: Generate the HTML Report + +**Explanation:** +The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings. + +**File:** `prowler/lib/outputs/html/html.py` + +This step is the same as the [SDK providers](#step-15-generate-the-html-report). + +#### Step 16: Add the Check Report Model + +**Explanation:** +Add the provider to the generic models, this is needed to be able to use the provider in the generic checks. + +**File:** `prowler/providers/check/models.py` + +This step is the same as the [SDK providers](#step-16-add-the-check-report-model). + +#### Step 17: Create Tests + +**Explanation:** +Testing ensures that your API provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover authentication, session management, and API-specific functionality. + +**Folder:** `tests/providers//` + +```python +import pytest +from prowler.providers.api_provider.api_provider import APIProvider + +class TestAPIProvider: + """Test cases for APIProvider.""" + + def test_provider_initialization(self): + """Test provider initialization with valid credentials.""" + provider = APIProvider( + username="test_user", + password="test_password", + tenant_id="test_tenant" + ) + assert provider.type == "your_api_provider" + assert provider.identity is not None + assert provider.session is not None + + def test_connection_test(self): + """Test connection functionality.""" + result = APIProvider.test_connection( + username="test_user", + password="test_password", + tenant_id="test_tenant" + ) + # Add assertions based on expected behavior + + def test_argument_validation(self): + """Test argument validation.""" + from prowler.providers.api_provider.api_provider import ( + APIProvider + ) + + # Valid arguments + APIProvider.validate_arguments( + username="test_user", + password="test_password", + tenant_id="test_tenant" + ) + + # Invalid arguments + with pytest.raises(ValueError, match="requires username, password and tenant_id"): + APIProvider.validate_arguments("", "", "") + + def test_session_setup(self): + """Test session setup.""" + provider = APIProvider( + username="test_user", + password="test_password", + tenant_id="test_tenant" + ) + assert provider.session is not None + assert "X-Auth-Token" in provider.session.headers +``` + +#### Step 18: Update Documentation + +**Explanation:** +Documentation updates ensure that users can find information about your API provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information. + +Update the provider documentation to include your new API provider in the examples and implementation guidance. + +--- + +### Tool/Wrapper Providers + +**Definition:** + +- Integrate third-party tools as libraries or subprocesses (e.g., Trivy for IaC). +- Examples: IaC (Trivy). + +**Typical Use Cases:** + +- Providers that require integration with external security tools. +- Tools that need to be executed as subprocesses or imported as libraries. +- Providers that require specific tool configurations and argument mapping. +- Legacy systems or tools that don't have direct API access. + +**Key Characteristics:** + +- No session/identity management required (tool handles this internally). +- Arguments: specific to the tool, but for example: `scan_path`, `frameworks`, `exclude_path`, `scan_repository_url`, etc. +- Outputs: Tool-specific output formats that need to be parsed and converted. +- Tool execution and output parsing. +- Configuration file mapping and argument translation. + +**Implementation Details:** + +- Tool providers typically execute external tools as subprocesses (e.g., `pwsh` or `trivy` command). +- They require mapping between Prowler's interface and the tool's arguments. +- Output parsing and conversion to Prowler's standard format is crucial. +- Tool-specific configuration files and validation. +- Repository cloning and temporary file management for remote scans (if needed). + +**Note:** This guide provides a general framework for integrating any external tool. The specific implementation details (like repository cloning, authentication tokens, etc.) will depend on your particular tool's requirements. The core pattern is: integrate with Prowler's CLI, execute your tool via subprocess, and parse the output into Prowler's format. + +--- + +### Implementation Guide for Tool/Wrapper Providers + +#### Step 1: Create the Provider Structure + +**Explanation:** +Tool/Wrapper providers require a specific folder structure to organize tool integration, configuration, and service management. This structure follows Prowler's conventions and ensures proper integration with the CLI and API. + +**Required Structure:** + +``` +prowler/providers// +├── __init__.py +├── _provider.py +├── models.py +└── lib/ + ├── __init__.py + └── arguments/ + ├── __init__.py + └── arguments.py +``` + +**Key Components:** + +- **`_provider.py`**: Main provider class with tool integration +- **`models.py`**: Data structures for tool output and configuration +- **`lib/arguments/`**: CLI argument validation and parsing + +#### Step 2: Implement the Provider Class + +**Explanation:** +The provider class is the core component that handles tool integration, execution, and output parsing. It inherits from Prowler's base Provider class and implements tool-specific execution flows using subprocesses or library calls. + +**File:** `prowler/providers//_provider.py` + +```python +import json +import subprocess +import sys +from typing import List + +from colorama import Fore, Style + +from prowler.config.config import ( + default_config_file_path, + load_and_validate_config_file, +) +from prowler.lib.check.models import CheckReportYourTool +from prowler.lib.logger import logger +from prowler.lib.utils.utils import print_boxes +from prowler.providers.common.models import Audit_Metadata +from prowler.providers.common.provider import Provider + +class ToolProvider(Provider): + """ + ToolProvider class is the main class for the Your Tool Provider. + + This class is responsible for initializing the provider, executing the external tool, + parsing tool output, and converting results to Prowler's standard format. + + Attributes: + _type (str): The provider type. + _session: Not used for tool providers. + _identity (str): Simple identity for tool providers. + _audit_config (dict): The audit configuration. + audit_metadata (Audit_Metadata): The audit metadata. + """ + + _type: str = "your_tool_provider" + audit_metadata: Audit_Metadata + + def __init__( + self, + # Tool-specific parameters + scan_path: str = ".", + tool_specific_arg: str = "default_value", + exclude_path: list[str] = [], + # Configuration + config_path: str = None, + config_content: dict = None, + fixer_config: dict = {}, + # Authentication (if needed for your tool) + auth_token: str = None, + auth_username: str = None, + ): + """ + Initializes the ToolProvider instance. + + Args: + scan_path: Path to the folder containing files to scan + tool_specific_arg: Tool-specific argument for your external tool + exclude_path: List of paths to exclude from scan + config_path: Path to the configuration file + config_content: Configuration content as dictionary + fixer_config: Fixer configuration dictionary + auth_token: Authentication token for your tool (if needed) + auth_username: Username for your tool (if needed) + + Raises: + ValueError: If required parameters are missing + """ + logger.info("Instantiating YourTool Provider...") + + # Store tool-specific parameters + self.scan_path = scan_path + self.tool_specific_arg = tool_specific_arg + self.exclude_path = exclude_path + self.region = "global" + self.audited_account = "local-tool" + self._session = None + self._identity = "prowler" + self._auth_method = "No auth" + + # Handle tool authentication if needed + if auth_token: + self.auth_token = auth_token + self._auth_method = "Token" + logger.info("Using token for tool authentication") + elif auth_username: + self.auth_username = auth_username + self._auth_method = "Username" + logger.info("Using username for tool authentication") + logger.info("Using username for tool authentication") + else: + logger.debug("No authentication method provided; proceeding without authentication.") + + # Audit Config + if config_content: + self._audit_config = config_content + else: + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file(self._type, config_path) + + # Fixer Config + self._fixer_config = fixer_config + + # Mutelist (not needed for tool providers since tools have their own mutelist logic) + self._mutelist = None + + Provider.set_global_provider(self) + + @property + def auth_method(self): + """Returns the authentication method used.""" + return self._auth_method + + @property + def type(self): + """Returns the type of the provider.""" + return self._type + + @property + def identity(self): + """Returns the provider identity.""" + return self._identity + + @property + def session(self): + """Returns the session (not used for tool providers).""" + return self._session + + @property + def audit_config(self): + """Returns the audit configuration.""" + return self._audit_config + + @property + def fixer_config(self): + """Returns the fixer configuration.""" + return self._fixer_config + + def setup_session(self): + """Tool providers don't need a session since they use external tools directly""" + return None + + def _process_check(self, finding: dict, check: dict, status: str) -> CheckReportYourTool: + """ + Process a single check (failed or passed) and create a CheckReportYourTool object. + + Args: + finding: The finding object from tool output + check: The individual check data + status: The status of the check ("FAIL", "PASS", or "MUTED") + + Returns: + CheckReportYourTool: The processed check report + """ + try: + metadata_dict = { + "Provider": "your_tool_provider", + "CheckID": check.get("check_id", ""), + "CheckTitle": check.get("check_name", ""), + "CheckType": ["Your Tool Provider"], + "ServiceName": finding["check_type"], + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": ( + check.get("severity", "low").lower() + if check.get("severity") + else "low" + ), + "ResourceType": "your_tool", + "Description": check.get("check_name", ""), + "Risk": "", + "RelatedUrl": ( + check.get("guideline", "") if check.get("guideline") else "" + ), + "Remediation": { + "Code": { + "NativeIaC": "", + "Terraform": "", + "CLI": "", + "Other": "", + }, + "Recommendation": { + "Text": "", + "Url": ( + check.get("guideline", "") if check.get("guideline") else "" + ), + }, + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + } + + # Convert metadata dict to JSON string + metadata = json.dumps(metadata_dict) + + report = CheckReportYourTool(metadata=metadata, finding=check) + report.status = status + report.resource_tags = check.get("entity_tags", {}) + report.status_extended = check.get("check_name", "") + if status == "MUTED": + report.muted = True + return report + except Exception as error: + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit(1) + + def run(self) -> List[CheckReportYourTool]: + """ + Main execution method that handles tool execution. + + Returns: + List[CheckReportYourTool]: List of check reports from the tool scan + """ + return self.run_scan(self.scan_path, self.exclude_path) + + def run_scan( + self, directory: str, exclude_path: list[str] + ) -> List[CheckReportYourTool]: + """ + Execute the external tool and parse its output. + + Args: + directory: Directory to scan + frameworks: List of frameworks to scan + exclude_path: List of paths to exclude + + Returns: + List[CheckReportYourTool]: List of check reports + """ + try: + logger.info(f"Running YourTool scan on {directory} ...") + + # Build the tool command + tool_command = [ + "your_tool_command", + # Add your tool-specific arguments here, this are just examples + "-d", + directory, + "-o", + "json", + "-f", + ",".join(frameworks), + ] + if exclude_path: + tool_command.extend(["--skip-path", ",".join(exclude_path)]) + + # Run the tool with JSON output + process = subprocess.run( + tool_command, + capture_output=True, + text=True, + ) + + # Log tool's error output if any + if process.stderr: + logger.error(process.stderr) + + try: + output = json.loads(process.stdout) + if not output: + logger.warning("No findings returned from YourTool scan") + return [] + except Exception as error: + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit(1) + + reports = [] + + # If only one framework has findings, the output is a dict, otherwise it's a list of dicts + if isinstance(output, dict): + output = [output] + + # Process all frameworks findings + for finding in output: + results = finding.get("results", {}) + + # Process failed checks + failed_checks = results.get("failed_checks", []) + for failed_check in failed_checks: + report = self._process_check(finding, failed_check, "FAIL") + reports.append(report) + + # Process passed checks + passed_checks = results.get("passed_checks", []) + for passed_check in passed_checks: + report = self._process_check(finding, passed_check, "PASS") + reports.append(report) + + # Process skipped checks (muted) + skipped_checks = results.get("skipped_checks", []) + for skipped_check in skipped_checks: + report = self._process_check(finding, skipped_check, "MUTED") + reports.append(report) + + return reports + + except Exception as error: + if "No such file or directory: 'your_tool_command'" in str(error): + logger.critical("Please, install your_tool using 'pip install your_tool'") + sys.exit(1) + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit(1) + + def print_credentials(self): + """ + Display scan information with color formatting. + + This method prints the tool scan information in a formatted way + using colorama for better readability. + """ + if self.scan_repository_url: + report_title = ( + f"{Style.BRIGHT}Scanning remote repository:{Style.RESET_ALL}" + ) + report_lines = [ + f"Repository: {Fore.YELLOW}{self.scan_repository_url}{Style.RESET_ALL}", + ] + else: + report_title = ( + f"{Style.BRIGHT}Scanning local directory:{Style.RESET_ALL}" + ) + report_lines = [ + f"Directory: {Fore.YELLOW}{self.scan_path}{Style.RESET_ALL}", + ] + + if self.exclude_path: + report_lines.append( + f"Excluded paths: {Fore.YELLOW}{', '.join(self.exclude_path)}{Style.RESET_ALL}" + ) + + report_lines.append( + f"Frameworks: {Fore.YELLOW}{', '.join(self.frameworks)}{Style.RESET_ALL}" + ) + + report_lines.append( + f"Authentication method: {Fore.YELLOW}{self.auth_method}{Style.RESET_ALL}" + ) + + print_boxes(report_lines, report_title) +``` + +#### Step 3: Create Models + +**Explanation:** +Models define the data structures used by your tool provider. They include output options and tool-specific configurations. These models ensure type safety and consistent data handling across the provider. + +**File:** `prowler/providers//models.py` + +```python +from prowler.config.config import output_file_timestamp +from prowler.providers.common.models import ProviderOutputOptions + +class YourToolOutputOptions(ProviderOutputOptions): + """ + YourToolOutputOptions overrides ProviderOutputOptions for tool-specific output logic. + For example, generating a filename that includes the tool name. + + Attributes inherited from ProviderOutputOptions: + - output_filename (str): The base filename used for generated reports. + - output_directory (str): The directory to store the output files. + - ... see ProviderOutputOptions for more details. + + Methods: + - __init__: Customizes the output filename logic for the tool provider. + """ + + def __init__(self, arguments, bulk_checks_metadata): + super().__init__(arguments, bulk_checks_metadata) + + # If --output-filename is not specified, build a default name. + if not getattr(arguments, "output_filename", None): + self.output_filename = f"prowler-output-your_tool-{output_file_timestamp}" + # If --output-filename was explicitly given, respect that + else: + self.output_filename = arguments.output_filename +``` + +#### Step 4: Implement Arguments + +**Explanation:** +Argument validation ensures that the tool provider receives valid configuration parameters. This step is crucial for preventing runtime errors and providing clear error messages to users. + +**File:** `prowler/providers//lib/arguments/arguments.py` + +```python +# Add your tool-specific choices if needed +TOOL_SPECIFIC_CHOICES = [ + "option1", + "option2", + "option3", + # Add your tool's supported options +] + +def init_parser(self): + """Init the Provider CLI parser""" + _parser = self.subparsers.add_parser( + "", parents=[self.common_providers_parser], help=" Provider" + ) + + # Scan Path + _scan_subparser = _parser.add_argument_group("Scan Path") + _scan_subparser.add_argument( + "--scan-path", + "-P", + dest="scan_path", + default=".", + help="Path to the folder containing your files to scan. Default: current directory.", + ) + + _scan_subparser.add_argument( + "--tool-specific-arg", + dest="tool_specific_arg", + default="default_value", + choices=TOOL_SPECIFIC_CHOICES, + help="Tool-specific argument for your external tool. Default: default_value", + ) + + _scan_subparser.add_argument( + "--exclude-path", + dest="exclude_path", + nargs="+", + default=[], + help="Comma-separated list of paths to exclude from the scan. Default: none", + ) + + # Authentication (if needed for your tool) + _scan_subparser.add_argument( + "--auth-token", + dest="auth_token", + nargs="?", + default=None, + help="Authentication token for your tool. If not provided, will use YOUR_TOOL_AUTH_TOKEN env var.", + ) + _scan_subparser.add_argument( + "--auth-username", + dest="auth_username", + nargs="?", + default=None, + help="Username for your tool authentication. If not provided, will use YOUR_TOOL_AUTH_USERNAME env var.", + ) + +def validate_arguments(arguments): + """ + Validate tool-specific arguments. + + Args: + arguments: The parsed arguments + + Returns: + tuple: (is_valid, error_message) + """ + scan_path = getattr(arguments, "scan_path", None) + scan_repository_url = getattr(arguments, "scan_repository_url", None) + + if scan_path and scan_repository_url: + # If scan_path is set to default ("."), allow scan_repository_url + if scan_path != ".": + return ( + False, + "--scan-path (-P) and --scan-repository-url (-R) are mutually exclusive. Please specify only one.", + ) + return (True, "") +``` + +#### Step 5: Register in CLI + +**Explanation:** +Add your provider to the available providers in the CLI. + +**File:** `prowler/lib/cli/parser.py` + +This step is the same as the [SDK providers](#step-9-register-in-cli). + +#### Step 6: Register in Main + +**Explanation:** +Main registration makes your provider discoverable by Prowler's core system. It's needed to add your provider to the output options and to the compliance evaluation. + +**File:** `prowler/__main__.py` + +This step is the same as the [SDK providers](#step-10-register-in-main). + +#### Step 7: Register in the list of providers + +**Explanation:** +This is needed to be able to use the provider in the generic checks. The provider must be registered in the `init_global_provider` method to handle CLI arguments and initialization. + +**File:** `prowler/providers/common/provider.py` + +This step is the same as the [SDK providers](#step-11-register-in-the-list-of-providers). + +#### Step 8: Add to Config + +**Explanation:** +Configuration registration ensures your tool provider is recognized by Prowler's configuration system. This enables proper handling of provider-specific settings and defaults. + +**File:** `prowler/config/config.py` + +This step is the same as the [SDK providers](#step-12-add-to-config). + +In some cases, you may need to create a new configuration file for your provider, for example, the AWS one that is inside `prowler/providers/aws/config.py`. + +#### Step 9: Create Compliance Files + +**Explanation:** +Compliance files define the security checks and standards that your provider supports. These JSON files map security controls to specific checks and provide remediation guidance. It's needed to create the folder with an init file to ensure the provider will work, however, adding different compliance files is optional. + +**Folder:** `prowler/compliance//` + +This step is the same as the [SDK providers](#step-13-create-compliance-files). + +#### Step 10: Add Output Support + +**Explanation:** +Output support ensures that your provider's results are properly formatted in Prowler's various output formats (CSV, JSON, HTML, etc.). This step integrates your provider with Prowler's reporting system. + +**File:** `prowler/lib/outputs/summary_table.py` + +This step is the same as the [SDK providers](#step-14-add-output-support). + +#### Step 11: Generate the HTML Report + +**Explanation:** +The HTML file is needed to be able to generate the HTML report. This step involves adding support for your provider in the HTML output generation system to ensure proper display of assessment summaries and findings. + +**File:** `prowler/lib/outputs/html/html.py` + +This step is the same as the [SDK providers](#step-15-generate-the-html-report). + +#### Step 12: Add the Check Report Model + +**Explanation:** +Add the provider to the generic models, this is needed to be able to use the provider in the generic checks. + +**File:** `prowler/providers/check/models.py` + +This step is the same as the [SDK providers](#step-16-add-the-check-report-model). + +#### Step 13: Create Tests + +**Explanation:** +Testing ensures that your tool provider works correctly and maintains compatibility as Prowler evolves. Comprehensive tests cover tool execution, output parsing, and provider-specific functionality. + +**Folder:** `tests/providers//` + +```python +import pytest +import tempfile +import os +from prowler.providers.your_tool_provider.your_tool_provider import ToolProvider + +class TestToolProvider: + """Test cases for ToolProvider.""" + + def test_provider_initialization(self): + """Test provider initialization with valid parameters.""" + provider = ToolProvider( + scan_path=".", + frameworks=["framework1"] + ) + assert provider.type == "your_tool_provider" + assert provider.identity == "prowler" + assert provider.scan_path == "." + + def test_tool_execution(self): + """Test tool execution and output parsing.""" + provider = ToolProvider(scan_path=".") + # Mock the subprocess call and test output parsing + # This will depend on your specific tool's output format + + def test_argument_validation(self): + """Test argument validation.""" + from prowler.providers.your_tool_provider.lib.arguments.arguments import ( + validate_arguments + ) + + # Valid arguments + class MockArgs: + scan_path = "." + tool_specific_arg = "value" + + is_valid, message = validate_arguments(MockArgs()) + assert is_valid is True + + # Add more test cases as needed for your specific tool provider + + def test_print_credentials(self): + """Test print_credentials method.""" + provider = ToolProvider( + scan_path="/test/path", + frameworks=["framework1"] + ) + # This should not raise any exceptions + provider.print_credentials() +``` + +#### Step 14: Update Documentation + +**Explanation:** +Documentation updates ensure that users can find information about your tool provider in Prowler's documentation. This includes examples, configuration guides, and troubleshooting information. + +Update the provider documentation to include your new tool provider in the examples and implementation guidance. + +--- + + +## Step 2: Integrate the Provider in the API + +This step is required only if you want your provider to be available in the API and UI. The API integration involves several components: + +### 2.1. Backend API Models + +**Location:** `api/src/backend/api/models.py` + +Add your provider to the `ProviderChoices` enum and implement UID validation: + +```python +class ProviderChoices(models.TextChoices): + AWS = "aws", "AWS" + AZURE = "azure", "Azure" + GCP = "gcp", "GCP" + KUBERNETES = "kubernetes", "Kubernetes" + M365 = "m365", "Microsoft 365" + GITHUB = "github", "GitHub" + NHN = "nhn", "NHN Cloud" + IAC = "iac", "Infrastructure as Code" + YOUR_PROVIDER = "your_provider", "Your Provider" # Add your provider here + +@staticmethod +def validate_your_provider_uid(value): + """Validate your provider UID format.""" + if not re.match(r"^your-regex-pattern$", value): + raise ModelValidationError( + detail="Your provider UID must follow the specified format.", + code="your-provider-uid", + pointer="/data/attributes/uid", + ) +``` + +**Provider Model:** +The `Provider` model already exists and supports all provider types. Ensure your provider type is included in the choices. + +### 2.2. Add the provider to the Provider Choices + +Update the `return_prowler_provider` function to include your provider. This function is crucial for the API to instantiate the correct provider class. + +**File:** `api/src/backend/api/utils.py` + +```python +from prowler.providers.your_provider.your_provider import YourProvider # Add your import + +def return_prowler_provider( + provider: Provider, +) -> [ + AwsProvider + | AzureProvider + | GcpProvider + | GithubProvider + | KubernetesProvider + | M365Provider + | YourProvider # Add your provider to the return type annotation +]: + """Return the Prowler provider class based on the given provider type.""" + match provider.provider: + case Provider.ProviderChoices.AWS.value: + prowler_provider = AwsProvider + case Provider.ProviderChoices.AZURE.value: + prowler_provider = AzureProvider + case Provider.ProviderChoices.GCP.value: + prowler_provider = GcpProvider + case Provider.ProviderChoices.KUBERNETES.value: + prowler_provider = KubernetesProvider + case Provider.ProviderChoices.M365.value: + prowler_provider = M365Provider + case Provider.ProviderChoices.GITHUB.value: + prowler_provider = GithubProvider + case Provider.ProviderChoices.YOUR_PROVIDER.value: # Add your provider here + prowler_provider = YourProvider + case _: + raise ValueError(f"Provider type {provider.provider} not supported") + return prowler_provider +``` + +**Also update the `initialize_prowler_provider` function:** + +```python +def initialize_prowler_provider( + provider: Provider, + mutelist_processor: Processor | None = None, +) -> ( + AwsProvider + | AzureProvider + | GcpProvider + | GithubProvider + | KubernetesProvider + | M365Provider + | YourProvider # Add your provider to the return type annotation +): + """Initialize a Prowler provider instance based on the given provider type.""" + prowler_provider = return_prowler_provider(provider) + prowler_provider_kwargs = get_prowler_provider_kwargs(provider, mutelist_processor) + return prowler_provider(**prowler_provider_kwargs) +``` + +**Note:** The `match` statement requires Python 3.10+. If you're using an older version, you can use traditional `if-elif` statements instead. + +### 2.3. API Serializers + +Create or update serializers for your provider. You'll need to add your provider to the validation logic: + +**File:** `api/src/backend/api/v1/serializers.py` + +```python +def validate_secret_based_on_provider(provider_type, secret): + """Validate provider-specific secrets.""" + if provider_type == Provider.ProviderChoices.AWS.value: + serializer = AWSProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.AZURE.value: + serializer = AzureProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.GCP.value: + serializer = GCPProviderSecret(data=secret) + elif provider_type == Provider.ProviderChoices.YOUR_PROVIDER.value: # Add your provider here + serializer = YourProviderSecret(data=secret) + # ... other providers + + if serializer.is_valid(): + return serializer.validated_data + else: + raise serializers.ValidationError(serializer.errors) + +class YourProviderSecret(serializers.Serializer): + """Serializer for your provider credentials.""" + your_auth_field = serializers.CharField(required=True) + your_optional_field = serializers.CharField(required=False) + + class Meta: + resource_name = "provider-secrets" +``` + +Also update the providers included in the serializer: + +**File:** `api/src/backend/api/v1/serializer_utils/providers.py` + +```python +@extend_schema_field( + { + "oneOf": [ + # ... existing provider schemas ... + { + "type": "object", + "title": "Your Provider Credentials", + "properties": { + "your_auth_field": { + "type": "string", + "description": "Your provider authentication field description.", + }, + "your_optional_field": { + "type": "string", + "description": "Optional field for your provider (if applicable).", + }, + "your_required_field": { + "type": "string", + "description": "Required field for your provider authentication.", + } + }, + "required": ["your_auth_field", "your_required_field"] + }, + # ... other existing schemas ... + ] + } +) +``` + +### 2.4. Database Migration + +Create a new migration to add your provider to the database. This is crucial for the API to recognize your provider type. + +**File:** `api/src/backend/api/migrations/XXXX_your_provider.py` + +```python +# Generated by Django X.X.X on YYYY-MM-DD + +from django.db import migrations + +import api.db_utils + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "previous_migration_name"), # Update this to the latest migration + ] + + operations = [ + migrations.AlterField( + model_name="provider", + name="provider", + field=api.db_utils.ProviderEnumField( + choices=[ + ("aws", "AWS"), + ("azure", "Azure"), + ("gcp", "GCP"), + ("kubernetes", "Kubernetes"), + ("m365", "M365"), + ("github", "GitHub"), + ("your_provider", "Your Provider"), # Add your provider here + ], + default="aws", + ), + ), + migrations.RunSQL( + "ALTER TYPE provider ADD VALUE IF NOT EXISTS 'your_provider';", + reverse_sql=migrations.RunSQL.noop, + ), + ] +``` + +**Important Notes:** + +- **Migration Number**: Use the next sequential number (e.g., if latest is 0044, use 0045) +- **Dependencies**: Update the `dependencies` list to point to the most recent migration +- **Choices Array**: Add your provider to the `choices` array with proper display name +- **SQL Operation**: The `RunSQL` operation adds your provider to the PostgreSQL enum type +- **Reverse SQL**: Use `migrations.RunSQL.noop` since adding enum values cannot be easily reversed + +**Migration Naming Convention:** + +- Format: `XXXX_your_provider.py` (e.g., `0045_your_provider.py`) +- Use descriptive names that indicate what the migration does +- Follow the existing pattern in the migrations folder + +### 2.5. Update the V1 Yaml + +Update the OpenAPI specification (`v1.yaml`) to include your provider in all relevant endpoints and schemas. This is crucial for API documentation and client generation. + +**File:** `api/src/backend/api/specs/v1.yaml` + +#### 2.5.1. Provider Enum Values + +Add your provider to the provider enum in the Provider schema: + +```yaml +# Around line 12150 in v1.yaml +Provider: + type: object + properties: + attributes: + properties: + provider: + enum: + - aws + - azure + - gcp + - kubernetes + - m365 + - github + - your_provider # Add your provider here + type: string + description: |- + * `aws` - AWS + * `azure` - Azure + * `gcp` - GCP + * `kubernetes` - Kubernetes + * `m365` - M365 + * `github` - GitHub + * `your_provider` - Your Provider # Add your provider here +``` + +#### 2.5.2. Provider Credential Schemas + +Add your provider's credential schema to the integration configuration. This defines how your provider's credentials are structured: + +```yaml +# Around line 11100 in v1.yaml, in the integration configuration +- type: object + title: Your Provider Credentials # Add your provider here + properties: + your_auth_field: + type: string + description: Your provider authentication field description. + your_optional_field: + type: string + description: Optional field for your provider (if applicable). + your_required_field: + type: string + description: Required field for your provider authentication. + required: + - your_auth_field + - your_required_field +``` + +#### 2.5.3. Example Provider Schemas + +Here are examples of how existing providers are documented: + +**AWS Provider:** +```yaml +- type: object + title: AWS Static Credentials + properties: + aws_access_key_id: + type: string + description: The AWS access key ID. + aws_secret_access_key: + type: string + description: The AWS secret access key. + required: + - aws_access_key_id + - aws_secret_access_key + +- type: object + title: AWS Assume Role + properties: + role_arn: + type: string + description: The Amazon Resource Name (ARN) of the role to assume. + external_id: + type: string + description: An identifier to enhance security for role assumption. + required: + - role_arn + - external_id +``` + +**GitHub Provider:** +```yaml +- type: object + title: GitHub Personal Access Token + properties: + personal_access_token: + type: string + description: GitHub personal access token for authentication. + required: + - personal_access_token + +- type: object + title: GitHub OAuth App Token + properties: + oauth_app_token: + type: string + description: GitHub OAuth App token for authentication. + required: + - oauth_app_token +``` + +**M365 Provider:** +```yaml +- type: object + title: M365 Static Credentials + properties: + client_id: + type: string + description: The Azure application (client) ID for authentication in Azure AD. + client_secret: + type: string + description: The client secret associated with the application (client) ID. + tenant_id: + type: string + description: The Azure tenant ID, representing the directory where the application is registered. + user: + type: email + description: User microsoft email address. + password: + type: string + description: User password. + required: + - client_id + - client_secret + - tenant_id + - user + - password +``` + +#### 2.5.4. Important Notes + +- **Position**: Add your schema in the `oneOf` array alongside existing providers +- **Structure**: Follow the exact pattern of other providers (title, properties, required fields) +- **Descriptions**: Provide clear, helpful descriptions for each field +- **Required Fields**: Specify which fields are mandatory in the `required` array +- **Field Types**: Use appropriate JSON schema types (`string`, `integer`, `boolean`, `email`, etc.) +- **Validation**: Add any field-specific validation patterns or constraints +- **Documentation**: Ensure your provider appears in the generated API documentation + +### 2.6. Testing API Integration + +Create tests for your provider: + +**Location:** `api/src/backend/api/tests/` + +```python +class YourProviderAPITestCase(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpass') + self.client.force_authenticate(user=self.user) + + def test_create_your_provider(self): + data = { + 'provider': 'your_provider', + 'uid': 'valid-uid-123', + 'alias': 'Test Account' + } + response = self.client.post('/api/v1/providers/', data) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.data['provider'], 'your_provider') + + def test_your_provider_uid_validation(self): + """Test UID validation for your provider.""" + invalid_uids = [ + 'invalid@uid', + '-invalid-start', + 'a' * 40, # Too long + ] + + for invalid_uid in invalid_uids: + data = { + 'provider': 'your_provider', + 'uid': invalid_uid, + 'alias': 'Test' + } + response = self.client.post('/api/v1/providers/', data) + self.assertEqual(response.status_code, 400) + self.assertIn('your-provider-uid', str(response.data)) + + def test_add_your_provider_credentials(self): + # Create provider first + provider = Provider.objects.create( + user=self.user, + provider='your_provider', + uid='valid-uid-123' + ) + + # Add credentials + credentials_data = { + 'secret_type': 'your_provider_credentials', + 'secret': { + 'your_auth_field': 'auth_value', + 'your_optional_field': 'optional_value' + }, + 'provider': provider.id + } + response = self.client.post('/api/v1/providers/secrets/', credentials_data) + self.assertEqual(response.status_code, 201) +``` + +#### 2.6.1. Add your mocked provider to the tests + +If needed, add your mocked provider to the tests config file so you can use it on the tests. + +**File:** `api/src/backend/conftest.py` + +```python +@pytest.fixture +def providers_fixture(tenants_fixture): + tenant, *_ = tenants_fixture + providerX = Provider.objects.create( + provider="your_provider", + uid="your_uid", + alias="your_alias", + tenant_id=tenant.id, + ) + return provider1, provider2, provider3, ... providerX +``` + +### 2.7. Compliance and Output Support + +Add your provider to the compliance export functionality: + +**File:** `api/src/backend/tasks/jobs/export.py` + +```python +COMPLIANCE_FRAMEWORKS = { + "aws": [...], + "azure": [...], + "gcp": [...], + "kubernetes": [...], + "m365": [...], + "github": [...], + "your_provider": [ # Add your provider here + (lambda name: name.startswith("cis_"), YourProviderCIS), + (lambda name: name.startswith("iso27001_"), YourProviderISO27001), + ], +} +``` + +If your provider has specific fields, add them to the finding transformation: + +**File:** `prowler/lib/outputs/finding.py` + +```python +def transform_api_finding(cls, finding, provider) -> "Finding": + # ... existing code ... + + # Your provider specific field + if provider.type == "your_provider": + finding.your_field = resource.your_field + + # ... rest of the code ... +``` + +### 2.8. API Endpoints + +Your provider will be available through these endpoints: + +- `GET /api/v1/providers/` - List all providers +- `POST /api/v1/providers/` - Create a new provider +- `GET /api/v1/providers/{id}/` - Get provider details +- `PUT /api/v1/providers/{id}/` - Update provider +- `DELETE /api/v1/providers/{id}/` - Delete provider +- `POST /api/v1/providers/secrets/` - Add provider credentials + +### 2.9. Update the provider if needed + +Depending on your provider's authentication requirements, you may need to add new authentication methods that are compatible with the API. This involves updating the provider class to support additional credential types beyond the basic ones. + +#### 2.9.1. Adding New Authentication Methods + +If your provider requires specific authentication methods, you'll need to: + +1. **Update the provider constructor** to accept new authentication parameters +2. **Extend the credential handling** to support the new authentication method +3. **Update the API serializers** to include the new credential fields +4. **Modify the OpenAPI specification** to document the new authentication schema + +#### 2.9.2. Example: GitHub Provider Authentication Methods + +The GitHub provider demonstrates how to implement multiple authentication methods: + +```python +# In prowler/providers/github/github_provider.py +def __init__( + self, + # Authentication methods + personal_access_token: str = "", + oauth_app_token: str = "", + github_app_key: str = "", + #Needed for the API integration + github_app_key_content: str = "", + github_app_id: int = 0, + # Provider configuration + config_path: str = None, + # ... other parameters +): + """ + Initialize GitHub provider. + + Args: + personal_access_token (str): GitHub personal access token. + oauth_app_token (str): GitHub OAuth App token. + github_app_key (str): GitHub App key. + github_app_key_content (str): GitHub App key content. + github_app_id (int): GitHub App ID. + config_path (str): Path to the audit configuration file. + # ... other parameters + """ + super().__init__( + personal_access_token, + oauth_app_token, + github_app_id, + github_app_key, + github_app_key_content, + ) +``` + +--- + +## Step 3: Integrate the Provider in the UI + +TBD + +--- + +## Provider Implementation Guidance + +Use existing providers as templates, this will help you to understand better the structure and the implementation will be easier: + +- [AWS (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py) +- [Azure (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py) +- [GCP (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py) +- [Kubernetes (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py) +- [M365 (SDK/Wrapper)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/m365_provider.py) +- [GitHub (SDK)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/github/github_provider.py) +- [NHN (API)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/nhn/nhn_provider.py) +- [IAC (Tool)](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/iac/iac_provider.py) +- [MongoDB Atlas](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/mongodbatlas/mongodbatlas_provider.py) + +--- + +## Best Practices + +- **Code Quality & Documentation** + + - **Comprehensive Docstrings**: Every class, method, and function should have detailed docstrings following Prowler's format + ```python + def method_name(self, param: str) -> str: + """ + Brief description of what the method does. + + Args: + param: Description of the parameter + + Returns: + Description of the return value + + Raises: + ExceptionType: When and why this exception occurs + """ + ``` + + - **Type Hints**: Use type hints for all function parameters and return values + ```python + from typing import Optional, List, Dict, Any + ``` + + - **Logging**: Implement proper logging using Prowler's logger + ```python + from prowler.lib.logger import logger + + logger.info("Operation completed successfully") + logger.warning("Something to be aware of") + logger.error("Something went wrong") + logger.critical("Critical error that may cause failure") + ``` + +- **Error Handling & Validation** + + - **Custom Exceptions**: Create provider-specific exceptions for better error handling + - **Input Validation**: Validate all inputs and provide clear error messages + - **Graceful Degradation**: Handle errors gracefully without crashing the entire scan + - **Raise on Exception**: Use `raise_on_exception` parameter for test methods + +- **Testing & Quality Assurance** + + - **Comprehensive Test Coverage**: Aim for >80% test coverage + - **Test Naming**: Use descriptive test names: `test_method_name_scenario` + - **Test Organization**: Group related tests in test classes + - **Mock External Dependencies**: Mock external API calls and services + - **Test Edge Cases**: Include tests for error conditions and edge cases + - **End-to-End Testing**: Test the provider on real infrastructure + +- **Performance & Security** + + - **Session Management**: Reuse sessions when possible, don't create new ones unnecessarily + - **Rate Limiting**: Implement rate limiting for API calls to avoid hitting limits + - **Resource Cleanup**: Ensure proper cleanup of temporary resources + - **Authentication Security**: Never log sensitive credentials or tokens + +- **Code Organization** + + - **Single Responsibility**: Each method should have one clear purpose + - **Consistent Naming**: Follow Prowler's naming conventions + - **Modular Design**: Break complex functionality into smaller, testable methods + - **Configuration Management**: Use configuration files for provider-specific settings + +- **Documentation & Maintenance** + + - **README Updates**: Update provider-specific documentation + - **Changelog**: Document changes and new features + - **Examples**: Provide usage examples and common scenarios + - **Troubleshooting**: Include common issues and solutions + - **Documentation**: Update the provider documentation to include your new tool provider in the examples and implementation guidance. + +- **Integration Standards** + + - **CLI Consistency**: Follow Prowler's CLI argument patterns + - **Output Format**: Ensure outputs are compatible with Prowler's reporting system + - **Compliance Mapping**: Map provider checks to relevant compliance frameworks + - **Backward Compatibility**: Maintain compatibility when possible + +- **AI-Assisted Development** + + - **Use Rules**: Use rules to ensure the code generated by AI is following the way of working in Prowler. + +## Checklist for New Providers + +### CLI Integration Only + +**Phase 1: Research & Planning** + +- [ ] Soft research completed +- [ ] Spike date scheduled +- [ ] Deeper research completed +- [ ] Action plan created + +**Phase 2: Implementation** + +- [ ] Folder and files created in `prowler/providers/` +- [ ] Provider class implemented and inherits from `Provider` +- [ ] Authentication/session logic implemented +- [ ] Arguments/flags mapped and documented +- [ ] Outputs and metadata standardized +- [ ] Registered in the CLI +- [ ] Minimal usage example provided + +**Phase 3: Delivery** + +- [ ] PoC delivered +- [ ] MVP delivered +- [ ] Version 1 completed +- [ ] QA and documentation completed +- [ ] GA release ready + +### API Integration + +- [ ] All CLI integration items completed +- [ ] Provider added to `ProviderChoices` enum in API models +- [ ] API serializers created/updated for the provider +- [ ] API views support the new provider type +- [ ] Provider credentials model supports the new provider +- [ ] API endpoints tested and working +- [ ] Provider-specific validation implemented +- [ ] API tests created and passing + +### UI Integration + +- TBD + +--- + +## Next Steps + +- [How to add a new Service](./services) +- [How to add new Checks](./checks) +- [How to contribute](./introduction#contributing-to-prowler) diff --git a/docs/developer-guide/services.mdx b/docs/developer-guide/services.mdx index 8f7382f0e9..ac7088dda0 100644 --- a/docs/developer-guide/services.mdx +++ b/docs/developer-guide/services.mdx @@ -5,8 +5,7 @@ title: 'Prowler Services' Here you can find how to create a new service, or to complement an existing one, for a [Prowler Provider](/developer-guide/provider). -First ensure that the provider you want to add the service is already created. It can be checked [here](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). If the provider is not present, please refer to the [Provider](/developer-guide/provider) documentation to create it from scratch. - +First ensure that the provider you want to add the service is already created. It can be checked [here](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers). If the provider is not present, please refer to the [Provider](./provider.md) documentation to create it from scratch. ## Introduction @@ -201,11 +200,11 @@ class (BaseModel): #### Service Attributes -*Optimized Data Storage with Python Dictionaries* +_Optimized Data Storage with Python Dictionaries_ Each group of resources within a service should be structured as a Python [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) to enable efficient lookups. The dictionary lookup operation has [O(1) complexity](https://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions), and lookups are constantly executed. -*Assigning Unique Identifiers* +_Assigning Unique Identifiers_ Each dictionary key must be a unique ID to identify the resource in a univocal way. @@ -241,6 +240,301 @@ Provider-Specific Permissions Documentation: - [M365](/user-guide/providers/microsoft365/authentication#required-permissions) - [GitHub](/user-guide/providers/github/authentication) +## Service Architecture and Cross-Service Communication + +### Core Principle: Service Isolation with Client Communication + +Each service must contain **ONLY** the information unique to that specific service. When a check requires information from multiple services, it must use the **client objects** of other services rather than directly accessing their data structures. + +This architecture ensures: + +- **Loose coupling** between services +- **Clear separation of concerns** +- **Maintainable and testable code** +- **Consistent data access patterns** + +### Cross-Service Communication Pattern + +Instead of services directly accessing each other's internal data, checks should import and use client objects: + +**❌ INCORRECT - Direct data access:** + +```python +# DON'T DO THIS +from prowler.providers.aws.services.cloudtrail.cloudtrail_service import cloudtrail_service +from prowler.providers.aws.services.s3.s3_service import s3_service + +class cloudtrail_bucket_requires_mfa_delete(Check): + def execute(self): + # WRONG: Directly accessing service data + for trail in cloudtrail_service.trails.values(): + for bucket in s3_service.buckets.values(): + # Direct access violates separation of concerns +``` + +**✅ CORRECT - Client-based communication:** + +```python +# DO THIS INSTEAD +from prowler.providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client +from prowler.providers.aws.services.s3.s3_client import s3_client + +class cloudtrail_bucket_requires_mfa_delete(Check): + def execute(self): + # CORRECT: Using client objects for cross-service communication + for trail in cloudtrail_client.trails.values(): + trail_bucket = trail.s3_bucket + for bucket in s3_client.buckets.values(): + if trail_bucket == bucket.name: + # Use bucket properties through s3_client + if bucket.mfa_delete: + # Implementation logic +``` + +### Real-World Example: CloudTrail + S3 Integration + +This example demonstrates how CloudTrail checks validate S3 bucket configurations: + +```python +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.cloudtrail.cloudtrail_client import cloudtrail_client +from prowler.providers.aws.services.s3.s3_client import s3_client + +class cloudtrail_bucket_requires_mfa_delete(Check): + def execute(self): + findings = [] + if cloudtrail_client.trails is not None: + for trail in cloudtrail_client.trails.values(): + if trail.is_logging: + trail_bucket_is_in_account = False + trail_bucket = trail.s3_bucket + + # Cross-service communication: CloudTrail check uses S3 client + for bucket in s3_client.buckets.values(): + if trail_bucket == bucket.name: + trail_bucket_is_in_account = True + if bucket.mfa_delete: + report.status = "PASS" + report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) has MFA delete enabled." + + # Handle cross-account scenarios + if not trail_bucket_is_in_account: + report.status = "MANUAL" + report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket or out of Prowler's audit scope, please check it manually." + + findings.append(report) + return findings +``` + +**Key Benefits:** + +- **CloudTrail service** only contains CloudTrail-specific data (trails, configurations) +- **S3 service** only contains S3-specific data (buckets, policies, ACLs) +- **Check logic** orchestrates between services using their public client interfaces +- **Cross-account detection** is handled gracefully when resources span accounts + +### Service Consolidation Guidelines + +**When to combine services in the same file:** + +Implement multiple services as **separate classes in the same file** when two services are **practically the same** or one is a **direct extension** of another. + +**Example: S3 and S3Control** + +S3Control is an extension of S3 that provides account-level controls and access points. Both are implemented in `s3_service.py`: + +```python +# File: prowler/providers/aws/services/s3/s3_service.py + +class S3(AWSService): + """Standard S3 service for bucket operations""" + def __init__(self, provider): + super().__init__(__class__.__name__, provider) + self.buckets = {} + self.regions_with_buckets = [] + + # S3-specific initialization + self._list_buckets(provider) + self._get_bucket_versioning() + # ... other S3-specific operations + +class S3Control(AWSService): + """S3Control service for account-level and access point operations""" + def __init__(self, provider): + super().__init__(__class__.__name__, provider) + self.account_public_access_block = None + self.access_points = {} + + # S3Control-specific initialization + self._get_public_access_block() + self._list_access_points() + # ... other S3Control-specific operations +``` + +**Separate client files:** + +```python +# File: prowler/providers/aws/services/s3/s3_client.py +from prowler.providers.aws.services.s3.s3_service import S3 +s3_client = S3(Provider.get_global_provider()) + +# File: prowler/providers/aws/services/s3/s3control_client.py +from prowler.providers.aws.services.s3.s3_service import S3Control +s3control_client = S3Control(Provider.get_global_provider()) +``` + +**When NOT to consolidate services:** + +Keep services separate when they: + +- **Operate on different resource types** (EC2 vs RDS) +- **Have different authentication mechanisms** (different API endpoints) +- **Serve different operational domains** (IAM vs CloudTrail) +- **Have different regional behaviors** (global vs regional services) + +### Cross-Service Dependencies Guidelines + +**1. Always use client imports:** + +```python +# Correct pattern +from prowler.providers.aws.services.service_a.service_a_client import service_a_client +from prowler.providers.aws.services.service_b.service_b_client import service_b_client +``` + +**2. Handle missing resources gracefully:** + +```python +# Handle cross-service scenarios +resource_found_in_account = False +for external_resource in other_service_client.resources.values(): + if target_resource_id == external_resource.id: + resource_found_in_account = True + # Process found resource + break + +if not resource_found_in_account: + # Handle cross-account or missing resource scenarios + report.status = "MANUAL" + report.status_extended = "Resource is cross-account or out of audit scope" +``` + +**3. Document cross-service dependencies:** + +```python +class check_with_dependencies(Check): + """ + Check Description + + Dependencies: + - service_a_client: For primary resource information + - service_b_client: For related resource validation + - service_c_client: For policy analysis + """ +``` + +## Regional Service Implementation + +When implementing services for regional providers (like AWS, Azure, GCP), special considerations are needed to handle resource discovery across multiple geographic locations. This section provides a complete guide using AWS as the reference example. + +### Regional vs Non-Regional Services + +**Regional Services:** Require iteration across multiple geographic locations where resources may exist (e.g., EC2 instances, VPC, RDS databases). + +**Non-Regional/Global Services:** Operate at a global or tenant level without regional concepts (e.g., IAM users, Route53 hosted zones). + +### AWS Regional Implementation Example + +AWS is the perfect example of a regional provider. Here's how Prowler handles AWS's regional architecture: + + +```python +# File: prowler/providers/aws/services/ec2/ec2_service.py +class EC2(AWSService): + def __init__(self, provider): + super().__init__(__class__.__name__, provider) + self.instances = {} + self.security_groups = {} + + # Regional resource discovery across all AWS regions + self.__threading_call__(self._describe_instances) + self.__threading_call__(self._describe_security_groups) + + def _describe_instances(self, regional_client): + """Discover EC2 instances in a specific region""" + try: + describe_instances_paginator = regional_client.get_paginator("describe_instances") + for page in describe_instances_paginator.paginate(): + for reservation in page["Reservations"]: + for instance in reservation["Instances"]: + # Each instance includes its region + self.instances[instance["InstanceId"]] = Instance( + id=instance["InstanceId"], + region=regional_client.region, + state=instance["State"]["Name"], + # ... other properties + ) + except Exception as error: + logger.error(f"Failed to describe instances in {regional_client.region}: {error}") +``` + +#### Regional Check Execution + +```python +# File: prowler/providers/aws/services/ec2/ec2_instance_public_ip/ec2_instance_public_ip.py +class ec2_instance_public_ip(Check): + def execute(self): + findings = [] + + # Automatically iterates across ALL AWS regions where instances exist + for instance in ec2_client.instances.values(): + report = Check_Report_AWS(metadata=self.metadata(), resource=instance) + report.region = instance.region # Critical: region attribution + report.resource_arn = f"arn:aws:ec2:{instance.region}:{instance.account_id}:instance/{instance.id}" + + if instance.public_ip: + report.status = "FAIL" + report.status_extended = f"Instance {instance.id} in {instance.region} has public IP {instance.public_ip}" + else: + report.status = "PASS" + report.status_extended = f"Instance {instance.id} in {instance.region} does not have a public IP" + + findings.append(report) + + return findings +``` + +#### Key AWS Regional Features + +**Region-Specific ARNs:** + +``` +arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0 +arn:aws:s3:eu-west-1:123456789012:bucket/my-bucket +arn:aws:rds:ap-southeast-2:123456789012:db:my-database +``` + +**Parallel Processing:** + +- Each region processed independently in separate threads +- Failed regions don't affect other regions +- User can filter specific regions: `-f us-east-1` + +**Global vs Regional Services:** + +- **Regional**: EC2, RDS, VPC (require region iteration) +- **Global**: IAM, Route53, CloudFront (single `us-east-1` call) + +This architecture allows Prowler to efficiently scan AWS accounts with resources spread across multiple regions while maintaining performance and error isolation. + +### Regional Service Best Practices + +1. **Use Threading for Regional Discovery**: Leverage the `__threading_call__` method to parallelize resource discovery across regions +2. **Store Region Information**: Always include region metadata in resource objects for proper attribution +3. **Handle Regional Failures Gracefully**: Ensure that failures in one region don't affect others +4. **Optimize for Performance**: Use paginated calls and efficient data structures for large-scale resource discovery +5. **Support Region Filtering**: Allow users to limit scans to specific regions for focused audits + ## Best Practices - When available in the provider, use threading or parallelization utilities for all methods that can be parallelized by to maximize performance and reduce scan time. @@ -252,3 +546,5 @@ Provider-Specific Permissions Documentation: - Collect and store resource tags and additional attributes to support richer checks and reporting. - Leverage shared utility helpers for session setup, identifier parsing, and other cross-cutting concerns to avoid code duplication. This kind of code is typically stored in a `lib` folder in the service folder. - Keep code modular, maintainable, and well-documented for ease of extension and troubleshooting. +- **Each service should contain only information unique to that specific service** - use client objects for cross-service communication. +- **Handle cross-account and missing resources gracefully** when checks span multiple services. diff --git a/docs/img/provider-decision-tree.png b/docs/img/provider-decision-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..f76d375f94c2caccf2d1ca017f6076e4669eb51f GIT binary patch literal 72934 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 z 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 literal 0 HcmV?d00001 diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index e4284da154..a53287bee1 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Oracle Cloud provider with CIS 3.0 benchmark [(#8893)](https://github.com/prowler-cloud/prowler/pull/8893) - Support for Atlassian Document Format (ADF) in Jira integration [(#8878)](https://github.com/prowler-cloud/prowler/pull/8878) - Add Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) +- Improve Provider documentation guide [(#8430)](https://github.com/prowler-cloud/prowler/pull/8430) - `cloudstorage_bucket_lifecycle_management_enabled` check for GCP provider [(#8936)](https://github.com/prowler-cloud/prowler/pull/8936) ### Changed From 0b7f02f7e4f39d77bf780f544b56edc266bddf04 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:38:25 +0200 Subject: [PATCH 50/57] feat: Check Findings component (#8976) Co-authored-by: Alan Buscaglia --- .../components/check-findings.tsx | 134 ++++++++++++++++++ ui/app/(prowler)/new-overview/page.tsx | 87 ++++++++++++ ui/components/graphs/donut-chart.tsx | 86 ++++++----- ui/components/graphs/shared/chart-tooltip.tsx | 60 ++------ ui/components/primitives.ts | 53 ------- ui/components/shadcn/README.md | 15 +- .../shadcn/card/base-card/base-card.tsx | 36 +++++ ui/components/shadcn/{ => card}/card.tsx | 9 +- .../resource-stats-card-container.tsx | 0 .../resource-stats-card-content.tsx | 16 +-- .../resource-stats-card-divider.tsx | 0 .../resource-stats-card-header.tsx | 0 .../resource-stats-card.tsx | 4 +- ui/components/shadcn/card/stats-container.tsx | 25 ++++ ui/components/shadcn/index.ts | 29 ++-- .../shadcn/resource-stats-card/index.ts | 13 -- ui/components/ui/chart/Chart.tsx | 3 +- ui/components/ui/custom/custom-button.tsx | 2 +- 18 files changed, 374 insertions(+), 198 deletions(-) create mode 100644 ui/app/(prowler)/new-overview/components/check-findings.tsx create mode 100644 ui/app/(prowler)/new-overview/page.tsx delete mode 100644 ui/components/primitives.ts create mode 100644 ui/components/shadcn/card/base-card/base-card.tsx rename ui/components/shadcn/{ => card}/card.tsx (89%) rename ui/components/shadcn/{ => card}/resource-stats-card/resource-stats-card-container.tsx (100%) rename ui/components/shadcn/{ => card}/resource-stats-card/resource-stats-card-content.tsx (89%) rename ui/components/shadcn/{ => card}/resource-stats-card/resource-stats-card-divider.tsx (100%) rename ui/components/shadcn/{ => card}/resource-stats-card/resource-stats-card-header.tsx (100%) rename ui/components/shadcn/{ => card}/resource-stats-card/resource-stats-card.tsx (98%) create mode 100644 ui/components/shadcn/card/stats-container.tsx delete mode 100644 ui/components/shadcn/resource-stats-card/index.ts diff --git a/ui/app/(prowler)/new-overview/components/check-findings.tsx b/ui/app/(prowler)/new-overview/components/check-findings.tsx new file mode 100644 index 0000000000..e57390afcf --- /dev/null +++ b/ui/app/(prowler)/new-overview/components/check-findings.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { Bell, BellOff, ShieldCheck, TriangleAlert } from "lucide-react"; + +import { DonutChart } from "@/components/graphs/donut-chart"; +import { DonutDataPoint } from "@/components/graphs/types"; +import { + BaseCard, + CardContent, + CardHeader, + CardTitle, + ResourceStatsCard, + StatsContainer, +} from "@/components/shadcn"; +import { CardVariant } from "@/components/shadcn/card/resource-stats-card/resource-stats-card-content"; + +interface CheckFindingsProps { + failFindingsData: { + total: number; + new: number; + muted: number; + }; + passFindingsData: { + total: number; + new: number; + muted: number; + }; +} + +export const CheckFindings = ({ + failFindingsData, + passFindingsData, +}: CheckFindingsProps) => { + // Calculate total findings + const totalFindings = failFindingsData.total + passFindingsData.total; + + // Calculate percentages + const failPercentage = Math.round( + (failFindingsData.total / totalFindings) * 100, + ); + const passPercentage = Math.round( + (passFindingsData.total / totalFindings) * 100, + ); + + // Calculate change percentages (new findings as percentage change) + const failChange = + failFindingsData.total > 0 + ? Math.round((failFindingsData.new / failFindingsData.total) * 100) + : 0; + const passChange = + passFindingsData.total > 0 + ? Math.round((passFindingsData.new / passFindingsData.total) * 100) + : 0; + + // Mock data for DonutChart + const donutData: DonutDataPoint[] = [ + { + name: "Fail Findings", + value: failFindingsData.total, + color: "#f43f5e", // Rose-500 + percentage: Number(failPercentage), + change: Number(failChange), + }, + { + name: "Pass Findings", + value: passFindingsData.total, + color: "#4ade80", // Green-400 + percentage: Number(passPercentage), + change: Number(passChange), + }, + ]; + + return ( + + {/* Header */} + + Check Findings + + + {/* DonutChart Content */} + +
+ +
+ + {/* Footer with ResourceStatsCards */} + + + +
+
+
+ + + + + + ); +}; diff --git a/ui/app/(prowler)/new-overview/page.tsx b/ui/app/(prowler)/new-overview/page.tsx new file mode 100644 index 0000000000..71fc62a024 --- /dev/null +++ b/ui/app/(prowler)/new-overview/page.tsx @@ -0,0 +1,87 @@ +import { Suspense } from "react"; + +import { getFindingsByStatus } from "@/actions/overview/overview"; +import { ContentLayout } from "@/components/ui"; +import { SearchParamsProps } from "@/types"; + +import { CheckFindings } from "./components/check-findings"; + +const FILTER_PREFIX = "filter["; + +// Extract only query params that start with "filter[" for API calls +function pickFilterParams( + params: SearchParamsProps | undefined | null, +): Record { + if (!params) return {}; + return Object.fromEntries( + Object.entries(params).filter(([key]) => key.startsWith(FILTER_PREFIX)), + ); +} + +export default async function NewOverviewPage({ + searchParams, +}: { + searchParams: Promise; +}) { + const resolvedSearchParams = await searchParams; + + return ( + +
+ +

Loading...

+
+ } + > + + +
+ + ); +} + +const SSRCheckFindings = async ({ + searchParams, +}: { + searchParams: SearchParamsProps | undefined | null; +}) => { + const filters = pickFilterParams(searchParams); + + const findingsByStatus = await getFindingsByStatus({ filters }); + + if (!findingsByStatus) { + return ( +
+

Failed to load findings data

+
+ ); + } + + const { + fail = 0, + pass = 0, + muted_new = 0, + muted_changed = 0, + fail_new = 0, + pass_new = 0, + } = findingsByStatus?.data?.attributes || {}; + + const mutedTotal = muted_new + muted_changed; + + return ( + + ); +}; diff --git a/ui/components/graphs/donut-chart.tsx b/ui/components/graphs/donut-chart.tsx index ea5ff49e5f..b82387944f 100644 --- a/ui/components/graphs/donut-chart.tsx +++ b/ui/components/graphs/donut-chart.tsx @@ -21,44 +21,39 @@ interface DonutChartProps { } const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length) { - const data = payload[0].payload; - return ( -
-
-
- - {data.percentage}% {data.name} - -
- {data.change !== undefined && ( -

- - {data.change > 0 ? "+" : ""} - {data.change}% - {" "} - Since last scan -

- )} + if (!active || !payload || !payload.length) return null; + + const entry = payload[0]; + const name = entry.name; + const percentage = entry.payload?.percentage; + const color = entry.color || entry.payload?.color; + const change = entry.payload?.change; + + return ( +
+
+
+ + {percentage}% + + {name}
- ); - } - return null; +

+ {change !== undefined && ( + <> + + {change > 0 ? "+" : ""} + {change}% + + Since Last Scan + + )} +

+
+ ); }; const CustomLegend = ({ payload }: any) => { @@ -72,8 +67,8 @@ const CustomLegend = ({ payload }: any) => { export function DonutChart({ data, - innerRadius = 80, - outerRadius = 120, + innerRadius = 68, + outerRadius = 86, showLegend = true, centerLabel, }: DonutChartProps) { @@ -108,7 +103,7 @@ export function DonutChart({ })); return ( -
+ <> {chartData.map((entry, index) => { const opacity = @@ -157,9 +152,9 @@ export function DonutChart({ {formattedValue} @@ -167,8 +162,9 @@ export function DonutChart({ {centerLabel.label} @@ -183,6 +179,6 @@ export function DonutChart({ {showLegend && } -
+ ); } diff --git a/ui/components/graphs/shared/chart-tooltip.tsx b/ui/components/graphs/shared/chart-tooltip.tsx index 432a15713d..558da987fe 100644 --- a/ui/components/graphs/shared/chart-tooltip.tsx +++ b/ui/components/graphs/shared/chart-tooltip.tsx @@ -29,7 +29,7 @@ export function ChartTooltip({ return (
)} -

+

{label || data.name}

-

+

{typeof data.value === "number" ? data.value.toLocaleString() : data.value} @@ -62,11 +59,8 @@ export function ChartTooltip({ {data.newFindings !== undefined && data.newFindings > 0 && (

- - + + {data.newFindings} New Findings
@@ -74,11 +68,8 @@ export function ChartTooltip({ {data.new !== undefined && data.new > 0 && (
- - + + {data.new} New
@@ -86,21 +77,15 @@ export function ChartTooltip({ {data.muted !== undefined && data.muted > 0 && (
- - + + {data.muted} Muted
)} {data.change !== undefined && ( -

+

{data.change > 0 ? "+" : ""} {data.change}% @@ -125,17 +110,8 @@ export function MultiSeriesChartTooltip({ } return ( -

-

+

+

{label}

@@ -145,20 +121,14 @@ export function MultiSeriesChartTooltip({ className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }} /> - + {entry.name}: - + {entry.value} {entry.payload[`${entry.dataKey}_change`] && ( - + ({entry.payload[`${entry.dataKey}_change`] > 0 ? "+" : ""} {entry.payload[`${entry.dataKey}_change`]}%) diff --git a/ui/components/primitives.ts b/ui/components/primitives.ts deleted file mode 100644 index 04b1f22594..0000000000 --- a/ui/components/primitives.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { tv } from "tailwind-variants"; - -export const title = tv({ - base: "tracking-tight inline font-semibold", - variants: { - color: { - violet: "from-[#FF1CF7] to-[#b249f8]", - yellow: "from-[#FF705B] to-[#FFB457]", - blue: "from-[#5EA2EF] to-[#0072F5]", - cyan: "from-[#00b7fa] to-[#01cfea]", - green: "from-[#6FEE8D] to-[#17c964]", - pink: "from-[#FF72E1] to-[#F54C7A]", - foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]", - }, - size: { - sm: "text-3xl lg:text-4xl", - md: "text-[2.3rem] lg:text-5xl leading-9", - lg: "text-4xl lg:text-6xl", - }, - fullWidth: { - true: "w-full block", - }, - }, - defaultVariants: { - size: "md", - }, - compoundVariants: [ - { - color: [ - "violet", - "yellow", - "blue", - "cyan", - "green", - "pink", - "foreground", - ], - class: "bg-clip-text text-transparent bg-linear-to-b", - }, - ], -}); - -export const subtitle = tv({ - base: "w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full", - variants: { - fullWidth: { - true: "w-full!", - }, - }, - defaultVariants: { - fullWidth: true, - }, -}); diff --git a/ui/components/shadcn/README.md b/ui/components/shadcn/README.md index 1bd28c8883..af3bc0348d 100644 --- a/ui/components/shadcn/README.md +++ b/ui/components/shadcn/README.md @@ -4,13 +4,18 @@ This directory contains all shadcn/ui based components for the Prowler applicati ## Directory Structure +Example of a custom component: + ``` shadcn/ -├── card.tsx # shadcn Card component -├── resource-stats-card/ # Custom ResourceStatsCard built on shadcn -│ ├── resource-stats-card.tsx -│ ├── resource-stats-card.example.tsx -│ └── index.ts +├── card/ +│ ├── base-card/ +│ │ ├── base-card.tsx +│ ├── card/ +│ │ ├── card.tsx +│ └── resource-stats-card/ +│ ├── resource-stats-card.tsx +│ ├── resource-stats-card.example.tsx ├── index.ts # Barrel exports └── README.md ``` diff --git a/ui/components/shadcn/card/base-card/base-card.tsx b/ui/components/shadcn/card/base-card/base-card.tsx new file mode 100644 index 0000000000..946e0cc184 --- /dev/null +++ b/ui/components/shadcn/card/base-card/base-card.tsx @@ -0,0 +1,36 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +import { Card } from "../card"; + +const baseCardVariants = cva("", { + variants: { + variant: { + default: + "border-slate-200 bg-white dark:border-zinc-900 dark:bg-stone-950", + }, + }, + defaultVariants: { + variant: "default", + }, +}); + +interface BaseCardProps + extends React.ComponentProps, + VariantProps {} + +const BaseCard = ({ className, variant, ...props }: BaseCardProps) => { + return ( + + ); +}; + +export { BaseCard }; diff --git a/ui/components/shadcn/card.tsx b/ui/components/shadcn/card/card.tsx similarity index 89% rename from ui/components/shadcn/card.tsx rename to ui/components/shadcn/card/card.tsx index a1b4a7742d..4165221253 100644 --- a/ui/components/shadcn/card.tsx +++ b/ui/components/shadcn/card/card.tsx @@ -1,5 +1,3 @@ -import * as React from "react"; - import { cn } from "@/lib/utils"; function Card({ className, ...props }: React.ComponentProps<"div">) { @@ -20,7 +18,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
) { return (
); diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card-container.tsx similarity index 100% rename from ui/components/shadcn/resource-stats-card/resource-stats-card-container.tsx rename to ui/components/shadcn/card/resource-stats-card/resource-stats-card-container.tsx diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card-content.tsx similarity index 89% rename from ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx rename to ui/components/shadcn/card/resource-stats-card/resource-stats-card-content.tsx index d165eb7944..7f7c0358d9 100644 --- a/ui/components/shadcn/resource-stats-card/resource-stats-card-content.tsx +++ b/ui/components/shadcn/card/resource-stats-card/resource-stats-card-content.tsx @@ -33,11 +33,11 @@ const badgeVariants = cva( { variants: { variant: { - [CardVariant.default]: "bg-[#535359]", - [CardVariant.fail]: "bg-[#432232]", - [CardVariant.pass]: "bg-[#204237]", - [CardVariant.warning]: "bg-[#3d3520]", - [CardVariant.info]: "bg-[#1e3a5f]", + [CardVariant.default]: "bg-slate-100 dark:bg-[#535359]", + [CardVariant.fail]: "bg-red-100 dark:bg-[#432232]", + [CardVariant.pass]: "bg-green-100 dark:bg-[#204237]", + [CardVariant.warning]: "bg-amber-100 dark:bg-[#3d3520]", + [CardVariant.info]: "bg-blue-100 dark:bg-[#1e3a5f]", }, size: { sm: "px-1 text-xs", @@ -66,7 +66,7 @@ const badgeIconVariants = cva("", { }); const labelTextVariants = cva( - "leading-6 font-semibold text-zinc-300 dark:text-zinc-300", + "leading-6 font-semibold text-slate-900 dark:text-zinc-300 whitespace-nowrap", { variants: { size: { @@ -81,7 +81,7 @@ const labelTextVariants = cva( }, ); -const statIconVariants = cva("text-zinc-300 dark:text-zinc-300", { +const statIconVariants = cva("text-slate-600 dark:text-zinc-300", { variants: { size: { sm: "h-2.5 w-2.5", @@ -95,7 +95,7 @@ const statIconVariants = cva("text-zinc-300 dark:text-zinc-300", { }); const statLabelVariants = cva( - "leading-5 font-medium text-zinc-300 dark:text-zinc-300", + "leading-5 font-medium text-slate-700 dark:text-zinc-300", { variants: { size: { diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card-divider.tsx similarity index 100% rename from ui/components/shadcn/resource-stats-card/resource-stats-card-divider.tsx rename to ui/components/shadcn/card/resource-stats-card/resource-stats-card-divider.tsx diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card-header.tsx similarity index 100% rename from ui/components/shadcn/resource-stats-card/resource-stats-card-header.tsx rename to ui/components/shadcn/card/resource-stats-card/resource-stats-card-header.tsx diff --git a/ui/components/shadcn/resource-stats-card/resource-stats-card.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx similarity index 98% rename from ui/components/shadcn/resource-stats-card/resource-stats-card.tsx rename to ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx index 7acbd103e3..4a1520d44d 100644 --- a/ui/components/shadcn/resource-stats-card/resource-stats-card.tsx +++ b/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx @@ -111,7 +111,7 @@ export const ResourceStatsCard = ({ {header && } {emptyState ? (
-

+

{emptyState.message}

@@ -141,7 +141,7 @@ export const ResourceStatsCard = ({ {header && } {emptyState ? (
-

+

{emptyState.message}

diff --git a/ui/components/shadcn/card/stats-container.tsx b/ui/components/shadcn/card/stats-container.tsx new file mode 100644 index 0000000000..9d37a60897 --- /dev/null +++ b/ui/components/shadcn/card/stats-container.tsx @@ -0,0 +1,25 @@ +import { cn } from "@/lib/utils"; + +interface StatsContainerProps extends React.HTMLAttributes { + children: React.ReactNode; +} + +const StatsContainer = ({ + className, + children, + ...props +}: StatsContainerProps) => { + return ( +
+ {children} +
+ ); +}; + +export { StatsContainer }; diff --git a/ui/components/shadcn/index.ts b/ui/components/shadcn/index.ts index 4291e07d5a..d29dc6f1dc 100644 --- a/ui/components/shadcn/index.ts +++ b/ui/components/shadcn/index.ts @@ -1,21 +1,8 @@ -export { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "./card"; -export { - ResourceStatsCard, - ResourceStatsCardContainer, - type ResourceStatsCardContainerProps, - ResourceStatsCardContent, - type ResourceStatsCardContentProps, - ResourceStatsCardDivider, - type ResourceStatsCardDividerProps, - ResourceStatsCardHeader, - type ResourceStatsCardHeaderProps, - type ResourceStatsCardProps, - type StatItem, -} from "./resource-stats-card"; +export * from "./card/base-card/base-card"; +export * from "./card/card"; +export * from "./card/resource-stats-card/resource-stats-card"; +export * from "./card/resource-stats-card/resource-stats-card-container"; +export * from "./card/resource-stats-card/resource-stats-card-content"; +export * from "./card/resource-stats-card/resource-stats-card-divider"; +export * from "./card/resource-stats-card/resource-stats-card-header"; +export * from "./card/stats-container"; diff --git a/ui/components/shadcn/resource-stats-card/index.ts b/ui/components/shadcn/resource-stats-card/index.ts deleted file mode 100644 index c049987ae0..0000000000 --- a/ui/components/shadcn/resource-stats-card/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type { ResourceStatsCardProps } from "./resource-stats-card"; -export { ResourceStatsCard } from "./resource-stats-card"; -export type { ResourceStatsCardContainerProps } from "./resource-stats-card-container"; -export { ResourceStatsCardContainer } from "./resource-stats-card-container"; -export type { - ResourceStatsCardContentProps, - StatItem, -} from "./resource-stats-card-content"; -export { ResourceStatsCardContent } from "./resource-stats-card-content"; -export type { ResourceStatsCardDividerProps } from "./resource-stats-card-divider"; -export { ResourceStatsCardDivider } from "./resource-stats-card-divider"; -export type { ResourceStatsCardHeaderProps } from "./resource-stats-card-header"; -export { ResourceStatsCardHeader } from "./resource-stats-card-header"; diff --git a/ui/components/ui/chart/Chart.tsx b/ui/components/ui/chart/Chart.tsx index fec25668b6..014fbc009d 100644 --- a/ui/components/ui/chart/Chart.tsx +++ b/ui/components/ui/chart/Chart.tsx @@ -70,6 +70,7 @@ const ChartContainer = React.forwardRef< ); }); + ChartContainer.displayName = "Chart"; const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { @@ -184,7 +185,7 @@ const ChartTooltipContent = React.forwardRef<
diff --git a/ui/components/ui/custom/custom-button.tsx b/ui/components/ui/custom/custom-button.tsx index 19d50ba686..8f71aabf24 100644 --- a/ui/components/ui/custom/custom-button.tsx +++ b/ui/components/ui/custom/custom-button.tsx @@ -86,7 +86,7 @@ export const CustomButton = React.forwardRef< ) => (

Tq8F``Ss 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%0Aq_*HPHuxX*8$fr2E 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 -`Directory.Read.All` can be replaced with `Domain.Read.All` for more restrictive permissions, but Entra checks related to DirectoryRoles and GetUsers will not run. If using this option, you must also add the `Organization.Read.All` permission to the service principal application for authentication. +`Directory.Read.All` can be replaced with `Domain.Read.All` for more restrictive permissions, but Entra checks related to DirectoryRoles and GetUsers will not run. If using this option, you must also add the `Organization.Read.All` permission to the application registration for authentication. -This is the **recommended authentication method** because it allows running the full M365 provider including PowerShell checks, providing complete coverage of all available security checks. +These permissions enable application-based authentication methods (client secret and certificate). Using certificate-based authentication is the recommended way to run the full M365 provider, including PowerShell checks. ### Browser Authentication Permissions @@ -47,96 +49,189 @@ This is the **recommended authentication method** because it allows running the When using browser authentication, permissions are delegated to the user, so the user must have the appropriate permissions rather than the application. -With browser authentication, you will only be able to run checks that work through MS Graph API. PowerShell module checks will not be executed. +Browser and Azure CLI authentication methods limit scanning capabilities to checks that operate through Microsoft Graph API. Checks requiring PowerShell modules will not execute, as they need application-level permissions that cannot be delegated through browser authentication. ### Step-by-Step Permission Assignment -#### Create Service Principal Application +#### Create Application Registration 1. Access **Microsoft Entra ID** - ![Overview of Microsoft Entra ID](/images/providers/microsoft-entra-id.png) + ![Overview of Microsoft Entra ID](/images/providers/microsoft-entra-id.png) 2. Navigate to "Applications" > "App registrations" - ![App Registration nav](/images/providers/app-registration-menu.png) + ![App Registration nav](/images/providers/app-registration-menu.png) 3. Click "+ New registration", complete the form, and click "Register" - ![New Registration](/images/providers/new-registration.png) + ![New Registration](/images/providers/new-registration.png) 4. Go to "Certificates & secrets" > "Client secrets" > "+ New client secret" - ![Certificate & Secrets nav](/images/providers/certificates-and-secrets.png) + ![Certificate & Secrets nav](/images/providers/certificates-and-secrets.png) 5. Fill in the required fields and click "Add", then copy the generated value (this will be `AZURE_CLIENT_SECRET`) - ![New Client Secret](/images/providers/new-client-secret.png) + ![New Client Secret](/images/providers/new-client-secret.png) #### Grant Microsoft Graph API Permissions 1. Go to App Registration > Select your Prowler App > click on "API permissions" - ![API Permission Page](/images/providers/api-permissions-page.png) + ![API Permission Page](/images/providers/api-permissions-page.png) 2. Click "+ Add a permission" > "Microsoft Graph" > "Application permissions" - ![Add API Permission](/images/providers/add-app-api-permission.png) + ![Add API Permission](/images/providers/add-app-api-permission.png) 3. Search and select the required permissions: - - `AuditLog.Read.All`: Required for Entra service - - `Directory.Read.All`: Required for all services - - `Policy.Read.All`: Required for all services - - `SharePointTenantSettings.Read.All`: Required for SharePoint service - ![Permission Screenshots](/images/providers/directory-permission.png) + - `AuditLog.Read.All`: Required for Entra service + - `Directory.Read.All`: Required for all services + - `Policy.Read.All`: Required for all services + - `SharePointTenantSettings.Read.All`: Required for SharePoint service - ![Application Permissions](/images/providers/app-permissions.png) + ![Permission Screenshots](/images/providers/directory-permission.png) -4. Click "Add permissions", then click "Grant admin consent for ````" + ![Application Permissions](/images/providers/app-permissions.png) -#### Grant PowerShell Module Permissions (For Service Principal Authentication) +4. Click "Add permissions", then click "Grant admin consent for ``" +#### Grant PowerShell Module Permissions 1. **Add Exchange API:** - - Search and select "Office 365 Exchange Online" API in **APIs my organization uses** + - Search and select "Office 365 Exchange Online" API in **APIs my organization uses** - ![Office 365 Exchange Online API](/images/providers/search-exchange-api.png) + ![Office 365 Exchange Online API](/images/providers/search-exchange-api.png) - - Select "Exchange.ManageAsApp" permission and click "Add permissions" + - Select "Exchange.ManageAsApp" permission and click "Add permissions" - ![Exchange.ManageAsApp Permission](/images/providers/exchange-permission.png) + ![Exchange.ManageAsApp Permission](/images/providers/exchange-permission.png) - - Assign `Global Reader` role to the app: Go to `Roles and administrators` > click `here` for directory level assignment + - Assign `Global Reader` role to the app: Go to `Roles and administrators` > click `here` for directory level assignment - ![Roles and administrators](/images/providers/here.png) + ![Roles and administrators](/images/providers/here.png) - - Search for `Global Reader` and assign it to your application + - Search for `Global Reader` and assign it to your application - ![Global Reader Role](/images/providers/global-reader-role.png) + ![Global Reader Role](/images/providers/global-reader-role.png) 2. **Add Teams API:** - - Search and select "Skype and Teams Tenant Admin API" in **APIs my organization uses** + - Search and select "Skype and Teams Tenant Admin API" in **APIs my organization uses** - ![Skype and Teams Tenant Admin API](/images/providers/search-skype-teams-tenant-admin-api.png) + ![Skype and Teams Tenant Admin API](/images/providers/search-skype-teams-tenant-admin-api.png) - - Select "application_access" permission and click "Add permissions" + - Select "application_access" permission and click "Add permissions" - ![application_access Permission](/images/providers/teams-permission.png) + ![application_access Permission](/images/providers/teams-permission.png) 3. Click "Grant admin consent for ``" to grant admin consent - ![Grant Admin Consent](/images/providers/grant-external-api-permissions.png) + ![Grant Admin Consent](/images/providers/grant-external-api-permissions.png) -## Service Principal Authentication (Recommended) +Final permissions should look like this: -*Available for both Prowler App and Prowler CLI* +![Final Permissions](/images/providers/final-permissions.png) + + + +## Application Certificate Authentication (Recommended) + +_Available for both Prowler App and Prowler CLI_ + +**Authentication flag for CLI:** `--certificate-auth` + +Certificate-based authentication replaces the client secret with an X.509 certificate that signs Microsoft Entra ID tokens for the Prowler application registration. + +This is the recommended approach for production environments because it avoids long-lived secrets, supports the full provider (including PowerShell checks), and simplifies unattended automation. Microsoft also recommends certificate credentials for app-only access, see [Manage certificates for applications](https://learn.microsoft.com/en-us/entra/identity-platform/certificate-credentials). + + +### Generate the Certificate + +The service principal needs a certificate that contains the private key locally (for Prowler) and the public key uploaded to Microsoft Entra ID. The following commands show a secure baseline workflow on macOS or Linux using OpenSSL: + +```console +# 1. Create a private key (keep this file private; do not upload it to the portal) +openssl genrsa -out prowlerm365.key 2048 + +# 2. Create a self-signed certificate valid for two years +openssl req -x509 -new -nodes -key prowlerm365.key -sha256 -days 730 -out prowlerm365.cer -subj "/CN=ProwlerM365Cert" + +# 3. Package the key and certificate into a passwordless PFX bundle for Prowler +openssl pkcs12 -export \ + -out prowlerm365.pfx \ + -inkey prowlerm365.key \ + -in prowlerm365.cer \ + -passout pass: +``` + + +Guard `prowlerm365.key` and `prowlerm365.pfx`. Only upload the `.cer` file to the Azure portal. Rotate or revoke the certificate before it expires or if there is any suspicion of exposure. + + + +If your organization uses a certificate authority, you can replace step 2 with a CSR workflow and import the signed certificate instead. + +### Upload the Certificate to Microsoft Entra ID + +1. Open **Microsoft Entra ID** > **App registrations** > your application. +2. Go to **Certificates & secrets** > **Certificates**. +3. Select **Upload certificate** and choose `prowlerm365.cer`. +4. Confirm the certificate appears with the expected expiration date. + +After the certificate is in place, encode the PFX file so it can be stored in an environment variable (macOS/Linux example): + +```console +base64 -i prowlerm365.pfx -o prowlerm365.pfx.b64 +cat prowlerm365.pfx.b64 | tr -d '\n' +``` + +Copy the resulting single-line Base64 string (or the contents of `prowlerm365.pfx.b64`)—you will use it in the next step. + +### Provide the Certificate to Prowler + +You can supply the private certificate to Prowler in two ways: + +- **Environment variables (recommended for headless execution)** + + ```console + export AZURE_CLIENT_ID="00000000-0000-0000-0000-000000000000" + export AZURE_TENANT_ID="11111111-1111-1111-1111-111111111111" + export M365_CERTIFICATE_CONTENT="$(base64 < prowlerm365.pfx | tr -d '\n')" + ``` + + The `M365_CERTIFICATE_CONTENT` variable must contain a single-line Base64 string. Remove any line breaks or spaces before exporting. + +- **Local file path** + + Store the PFX securely and reference it when you run the CLI: + + ```console + python3 prowler-cli.py m365 --certificate-auth --certificate-path /secure/path/prowlerm365.pfx + ``` + + The CLI still needs `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` in the environment when you use `--certificate-path`. + +For the **Prowler App**, paste the Base64-encoded PFX in the `certificate_content` field when you configure the provider secrets. The platform persists the encrypted certificate and supplies it during scans. + + +Do not mix certificate authentication with a client secret. Provide either a certificate **or** a secret to the application registration and Prowler configuration. + + + + + + +## Application Client Secret Authentication + +_Available for both Prowler App and Prowler CLI_ **Authentication flag for CLI:** `--sp-env-auth` -Authenticate using the **Service Principal Application** by configuring the following environment variables: +Authenticate using a **Microsoft Entra application registration with a client secret** by configuring the following environment variables: ```console export AZURE_CLIENT_ID="XXXXXXXXX" @@ -150,13 +245,61 @@ Refer to the [Step-by-Step Permission Assignment](#step-by-step-permission-assig If the external API permissions described in the mentioned section above are not added only checks that work through MS Graph will be executed. This means that the full provider will not be executed. +This workflow is helpful for initial validation or temporary access. Plan to transition to certificate-based authentication to remove long-lived secrets and keep full provider coverage in unattended environments. + -In order to scan all the checks from M365 required permissions to the service principal application must be added. Refer to the [PowerShell Module Permissions](#grant-powershell-module-permissions-for-service-principal-authentication) section for more information. +To scan every M365 check, ensure the required permissions are added to the application registration. Refer to the [PowerShell Module Permissions](#grant-powershell-module-permissions-for-app-only-authentication) section for more information. + +### Run Prowler with Certificate Authentication + +After the variables or path are in place, run the Microsoft 365 provider as usual: + +```console +python3 prowler-cli.py m365 --certificate-auth --init-modules --log-level ERROR +``` + +The command above initializes PowerShell modules if needed. You can combine other standard flags (for example, `--region M365USGovernment` or custom outputs) with `--certificate-auth`. + +Prowler prints the certificate thumbprint during execution so you can confirm the correct credential is in use. + + +## Azure CLI Authentication + +_Available only for Prowler CLI_ + +**Authentication flag for CLI:** `--az-cli-auth` + +Azure CLI authentication relies on the identity that is already signed in with the Azure CLI. Before running Prowler, make sure you have an active CLI session in the target tenant: + +```console +az login --tenant +# Optional: enforce the tenant when several are available +az account set --tenant +``` + +If you prefer to reuse the same service principal that powers certificate-based authentication, authenticate it through Azure CLI instead of exporting environment variables. Azure CLI expects the certificate in PEM format; convert the PFX produced earlier and sign in: + +```console +openssl pkcs12 -in prowlerm365.pfx -out prowlerm365.pem -nodes +az login --service-principal \ + --username \ + --password /secure/path/prowlerm365.pem \ + --tenant +``` + +After the CLI session is authenticated, launch Prowler with the Azure CLI flag: + +```console +python3 prowler-cli.py m365 --az-cli-auth +``` + +The Azure CLI identity must hold the same Microsoft Graph and external API permissions required for the full provider. Signing in with a user account limits the scan to delegated Microsoft Graph endpoints and skips PowerShell-based checks. Use a service principal with the necessary application permissions to keep complete coverage. + ## Interactive Browser Authentication -*Available only for Prowler CLI* +_Available only for Prowler CLI_ **Authentication flag:** `--browser-auth` @@ -171,6 +314,7 @@ Since this is a **delegated permission** authentication method, necessary permis PowerShell is required to run certain M365 checks. **Supported versions:** + - **PowerShell 7.4 or higher** (7.5 is recommended) #### Why Is PowerShell 7.4+ Required? @@ -193,6 +337,7 @@ Installing PowerShell is different depending on your OS: ```console winget install --id Microsoft.PowerShell --source winget ``` + [MacOS](https://learn.microsoft.com/es-es/powershell/scripting/install/installing-powershell-on-macos?view=powershell-7.5#install-the-latest-stable-release-of-powershell): installing PowerShell on MacOS needs to have installed [brew](https://brew.sh/), once installed, simply run the command shown above, Pwsh is only supported in macOS 15 (Sequoia) x64 and Arm64, macOS 14 (Sonoma) x64 and Arm64, macOS 13 (Ventura) x64 and Arm64 @@ -202,6 +347,7 @@ Installing PowerShell is different depending on your OS: ``` Once it's installed run `pwsh` on your terminal to verify it's working. + [Ubuntu](https://learn.microsoft.com/es-es/powershell/scripting/install/install-ubuntu?view=powershell-7.5#installation-via-package-repository-the-package-repository): The required version for installing PowerShell +7.4 on Ubuntu are Ubuntu 22.04 and Ubuntu 24.04. @@ -241,6 +387,7 @@ Installing PowerShell is different depending on your OS: # Start PowerShell pwsh ``` + [Alpine](https://learn.microsoft.com/es-es/powershell/scripting/install/install-alpine?view=powershell-7.5#installation-steps): The only supported version for installing PowerShell +7.4 on Alpine is Alpine 3.20. The unique way to install it is downloading the tar.gz package available on [PowerShell github](https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/powershell-7.5.0-linux-musl-x64.tar.gz). @@ -286,6 +433,7 @@ Installing PowerShell is different depending on your OS: # Start PowerShell pwsh ``` + [Debian](https://learn.microsoft.com/es-es/powershell/scripting/install/install-debian?view=powershell-7.5#installation-on-debian-11-or-12-via-the-package-repository): The required version for installing PowerShell +7.4 on Debian are Debian 11 and Debian 12. The recommended way to install it is downloading the package available on PMC. @@ -324,6 +472,7 @@ Installing PowerShell is different depending on your OS: # Start PowerShell pwsh ``` + [Rhel](https://learn.microsoft.com/es-es/powershell/scripting/install/install-rhel?view=powershell-7.5#installation-via-the-package-repository): The required version for installing PowerShell +7.4 on Red Hat are RHEL 8 and RHEL 9. The recommended way to install it is downloading the package available on PMC. @@ -357,6 +506,7 @@ Installing PowerShell is different depending on your OS: # Install PowerShell sudo dnf install powershell -y ``` + [Docker](https://learn.microsoft.com/es-es/powershell/scripting/install/powershell-in-docker?view=powershell-7.5#use-powershell-in-a-container): The following command download the latest stable versions of PowerShell: @@ -370,6 +520,7 @@ Installing PowerShell is different depending on your OS: ```console docker run -it mcr.microsoft.com/dotnet/sdk:9.0 pwsh ``` + ### Required PowerShell Modules @@ -386,6 +537,7 @@ Example command: ```console python3 prowler-cli.py m365 --verbose --log-level ERROR --sp-env-auth --init-modules ``` + If the modules are already installed, running this command will not cause issues—it will simply verify that the necessary modules are available. @@ -399,7 +551,6 @@ Install-Module -Name "ModuleName" -Scope AllUsers -Force #### Modules Version +- [MSAL.PS](https://www.powershellgallery.com/packages/MSAL.PS/4.32.0): Required for Exchange module via application authentication. - [ExchangeOnlineManagement](https://www.powershellgallery.com/packages/ExchangeOnlineManagement/3.6.0) (Minimum version: 3.6.0) Required for checks across Exchange, Defender, and Purview. - [MicrosoftTeams](https://www.powershellgallery.com/packages/MicrosoftTeams/6.6.0) (Minimum version: 6.6.0) Required for all Teams checks. -- [MSAL.PS](https://www.powershellgallery.com/packages/MSAL.PS/4.32.0): Required for Exchange module via application authentication. -- [MSAL.PS](https://www.powershellgallery.com/packages/MSAL.PS/4.32.0): Required for Exchange module via application authentication. diff --git a/docs/user-guide/providers/microsoft365/getting-started-m365.mdx b/docs/user-guide/providers/microsoft365/getting-started-m365.mdx index 67d57911b7..19844b9347 100644 --- a/docs/user-guide/providers/microsoft365/getting-started-m365.mdx +++ b/docs/user-guide/providers/microsoft365/getting-started-m365.mdx @@ -12,8 +12,9 @@ Government cloud accounts or tenants (Microsoft 365 Government) are currently un Configure authentication for Microsoft 365 by following the [Microsoft 365 Authentication](/user-guide/providers/microsoft365/authentication) guide. This includes: -- Creating a Service Principal Application -- Granting required Microsoft Graph API permissions +- Registering an application in Microsoft Entra ID +- Granting all required Microsoft Graph and external API permissions +- Generating the application certificate (recommended) or client secret - Setting up PowerShell module permissions (for full security coverage) ## Prowler App @@ -47,25 +48,38 @@ Configure authentication for Microsoft 365 by following the [Microsoft 365 Authe ![Add Domain ID](/images/providers/add-domain-id.png) -### Step 3: Add Credentials to Prowler App +### Step 3: Select Authentication Method and Provide Credentials -1. Go to App Registration overview and copy the Client ID and Tenant ID +Prowler App now separates Microsoft 365 authentication into two app-only options. After adding the Domain ID, choose the method that matches your setup: - ![App Overview](/images/providers/app-overview.png) +M365 authentication method selection -2. Go to Prowler App and paste: +#### Application Certificate Authentication (Recommended) - - Client ID - - Tenant ID - - `AZURE_CLIENT_SECRET` from the Service Principal setup +1. Copy the Application (client) ID and Tenant ID from the app registration overview page. +2. Paste both values into the Prowler App form. +3. Upload the PFX bundle or paste the Base64-encoded certificate (`M365_CERTIFICATE_CONTENT`), then click **Test Connection**. - ![Prowler Cloud M365 Credentials](/images/providers/m365-credentials.png) +M365 certificate authentication form -3. Click "Next" +Use this method whenever possible to avoid managing client secrets and to unlock every Microsoft 365 check, including those that require PowerShell modules. + +#### Application Client Secret Authentication + +1. From the app registration, copy the Application (client) ID and Tenant ID. +2. Paste both values plus the client secret into the Prowler App form. +3. Click **Test Connection** to validate the credentials. + +M365 client secret authentication form + + +### Step 4: Launch the Scan + +1. Review the summary, then click **Next**. ![Next Detail](/images/providers/click-next-m365.png) -4. Click "Launch Scan" +2. Click **Launch Scan** to start auditing Microsoft 365. ![Launch Scan M365](/images/providers/launch-scan.png) @@ -83,7 +97,9 @@ PowerShell 7.4+ is required for comprehensive Microsoft 365 security coverage. I Select an authentication method from the [Microsoft 365 Authentication](/user-guide/providers/microsoft365/authentication) guide: -- **Service Principal Application** (recommended): `--sp-env-auth` +- **Application Certificate Authentication** (recommended): `--certificate-auth` +- **Application Client Secret Authentication**: `--sp-env-auth` +- **Azure CLI Authentication**: `--az-cli-auth` - **Interactive Browser Authentication**: `--browser-auth` ### Basic Usage diff --git a/docs/user-guide/tutorials/prowler-app.mdx b/docs/user-guide/tutorials/prowler-app.mdx index 803ca0f2f2..dd2074c3b9 100644 --- a/docs/user-guide/tutorials/prowler-app.mdx +++ b/docs/user-guide/tutorials/prowler-app.mdx @@ -207,17 +207,28 @@ If you are adding an **EKS**, **GKE**, **AKS** or external cluster, follow these 4. Now you can add the modified `kubeconfig` in Prowler Cloud. Then test the connection. ### **Step 4.5: M365 Credentials** -For M365, you must enter your Domain ID and choose the authentication method you want to use: +Enter your Microsoft Entra domain (primary tenant domain) and select how the provider should authenticate. Prowler App guides you through the process: -- Service Principal Authentication (Recommended) +M365 authentication method selection - -User authentication with M365_USER and M365_PASSWORD is deprecated and will be removed. +- **Application Client Secret Authentication**: Client secret-based authentication. +- **Application Certificate Authentication (Recommended)**: Certificate-based authentication. Recommended by Microsoft. - -For full setup instructions and requirements, check the [Microsoft 365 provider requirements](/user-guide/providers/microsoft365/getting-started-m365). +#### Step 4.5.1: Application Client Secret Authentication +1. **Enter your tenant ID**: This is the unique identifier for your Microsoft Entra ID directory. +2. **Enter your application (client) ID**: This is the unique identifier assigned to your app registration in Microsoft Entra ID. +3. **Enter your client secret**: This is the secret key used to authenticate your application. -Prowler Cloud M365 Credentials +M365 client secret authentication form + +For full setup instructions, certificate generation commands, and required permissions, review the [Microsoft 365 provider requirements](/user-guide/providers/microsoft365/getting-started-m365). + +#### Step 4.5.2: Application Certificate Authentication (Recommended) +1. **Enter your tenant ID**: This is the unique identifier for your Microsoft Entra ID directory. +2. **Enter your application (client) ID**: This is the unique identifier assigned to your app registration in Microsoft Entra ID. +3. **Upload your certificate file content**: This is the **Base64** encoded certificate content used to authenticate your application. + +M365 certificate authentication form ### **Step 4.6: GitHub Credentials** For GitHub, you must enter your Provider ID (username or organization name) and choose the authentication method you want to use: From a3c811f8014560e8e99090b3592a37d84c4a2469 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Fri, 17 Oct 2025 09:30:54 -0400 Subject: [PATCH 02/57] docs(github): clarify GitHub App configuration requirements (#8930) --- .../providers/github/authentication.mdx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/providers/github/authentication.mdx b/docs/user-guide/providers/github/authentication.mdx index 597a90d658..d76a0a1cb5 100644 --- a/docs/user-guide/providers/github/authentication.mdx +++ b/docs/user-guide/providers/github/authentication.mdx @@ -144,10 +144,19 @@ GitHub Apps provide the recommended integration method for accessing multiple re 2. **Create New GitHub App** - Click "New GitHub App" - Complete the required fields: - - **GitHub App name**: Unique application name - - **Homepage URL**: Application homepage - - **Webhook URL**: Webhook payload URL (optional) - - **Permissions**: Application permission requirements + - **GitHub App name**: Choose a unique, descriptive name (e.g., "Prowler Security Scanner") + - **Homepage URL**: Enter your organization's website or the Prowler documentation URL (e.g., `https://prowler.com` or `https://docs.prowler.com`). This is just for reference and doesn't affect functionality. + - **Webhook URL**: Leave blank or uncheck "Active" under Webhook. Prowler doesn't require webhooks since it performs on-demand scans rather than responding to GitHub events. + - **Webhook secret**: Leave blank (not needed for Prowler) + - **Permissions**: Configure in the next step (see below) + + + **About Homepage URL and Webhooks** + + The Homepage URL is purely informational and can be any valid URL - it's just displayed to users who view the app. Use your company website, your GitHub organization URL, or even `https://docs.prowler.com`. + + Webhooks are **not required** for Prowler. Since Prowler performs on-demand security scans when you run it (rather than automatically responding to GitHub events), you can safely disable webhooks or leave the URL blank. + 3. **Configure Permissions** To enable Prowler functionality, configure these permissions: From a653ad7852facac272c8533a3c58c319324a581f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Fri, 17 Oct 2025 16:37:32 +0200 Subject: [PATCH 03/57] chore(deps): remove docs group dependency (#8937) --- poetry.lock | 359 +++++-------------------------------------------- pyproject.toml | 9 -- 2 files changed, 35 insertions(+), 333 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9451ead963..847a31f878 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "about-time" @@ -834,21 +834,6 @@ typing-extensions = ">=4.6.0" [package.extras] aio = ["azure-core[aio] (>=1.30.0)"] -[[package]] -name = "babel" -version = "2.17.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - [[package]] name = "bandit" version = "1.8.3" @@ -994,7 +979,7 @@ version = "2025.7.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2"}, {file = "certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995"}, @@ -1126,7 +1111,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -1240,7 +1225,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] markers = "python_version < \"3.10\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, @@ -1256,7 +1241,7 @@ version = "8.2.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] markers = "python_version >= \"3.10\"" files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, @@ -1290,7 +1275,7 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -1921,59 +1906,6 @@ files = [ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -groups = ["docs"] -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.45" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, - {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.10.0.2", markers = "python_version < \"3.10\""} - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] - [[package]] name = "google-api-core" version = "2.25.1" @@ -2258,7 +2190,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -2273,12 +2205,12 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] -markers = {dev = "python_version < \"3.10\"", docs = "python_version < \"3.10\""} +markers = {dev = "python_version < \"3.10\""} [package.dependencies] zipp = ">=3.20" @@ -2350,7 +2282,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2416,6 +2348,8 @@ python-versions = "*" groups = ["dev"] files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -2546,7 +2480,7 @@ version = "3.9" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" -groups = ["main", "docs"] +groups = ["main"] files = [ {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"}, {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"}, @@ -2590,7 +2524,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -2699,18 +2633,6 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -groups = ["docs"] -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - [[package]] name = "microsoft-kiota-abstractions" version = "1.9.2" @@ -2825,116 +2747,6 @@ files = [ [package.dependencies] microsoft-kiota-abstractions = ">=1.9.2,<1.10.0" -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, - {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mkdocs-git-revision-date-localized-plugin" -version = "1.4.1" -description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_git_revision_date_localized_plugin-1.4.1-py3-none-any.whl", hash = "sha256:bb1eca7f156e0c8a587167662923d76efed7f7e0c06b84471aa5ae72a744a434"}, - {file = "mkdocs_git_revision_date_localized_plugin-1.4.1.tar.gz", hash = "sha256:364d7c4c45c4f333c750e34bc298ac685a7a8bf9b7b52890d52b2f90f1812c4b"}, -] - -[package.dependencies] -babel = ">=2.7.0" -gitpython = ">=3.1.44" -mkdocs = ">=1.0" -pytz = ">=2025.1" - -[[package]] -name = "mkdocs-material" -version = "9.6.5" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b"}, - {file = "mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f"}, -] - -[package.dependencies] -babel = ">=2.10,<3.0" -colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" -markdown = ">=3.2,<4.0" -mkdocs = ">=1.6,<2.0" -mkdocs-material-extensions = ">=1.3,<2.0" -paginate = ">=0.5,<1.0" -pygments = ">=2.16,<3.0" -pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" -requests = ">=2.26,<3.0" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] -recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, -] - [[package]] name = "mock" version = "5.2.0" @@ -3582,28 +3394,12 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] -[[package]] -name = "paginate" -version = "0.5.7" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -groups = ["docs"] -files = [ - {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, - {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, -] - -[package.extras] -dev = ["pytest", "tox"] -lint = ["black"] - [[package]] name = "pandas" version = "2.2.3" @@ -3709,7 +3505,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -3736,7 +3532,7 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev", "docs"] +groups = ["dev"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -4264,7 +4060,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] +groups = ["dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -4325,25 +4121,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] -[[package]] -name = "pymdown-extensions" -version = "10.16" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2"}, - {file = "pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de"}, -] - -[package.dependencies] -markdown = ">=3.6" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.19.1)"] - [[package]] name = "pynacl" version = "1.5.0" @@ -4509,7 +4286,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -4524,7 +4301,7 @@ version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" -groups = ["main", "docs"] +groups = ["main"] files = [ {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, @@ -4567,7 +4344,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -4624,21 +4401,6 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -description = "A custom YAML tag for referencing environment variables in YAML files." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, - {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, -] - -[package.dependencies] -pyyaml = "*" - [[package]] name = "referencing" version = "0.36.2" @@ -4662,7 +4424,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] +groups = ["dev"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -4766,7 +4528,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -5085,6 +4847,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -5093,6 +4856,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -5101,6 +4865,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -5109,6 +4874,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -5117,6 +4883,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5268,7 +5035,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -5289,18 +5056,6 @@ files = [ [package.extras] optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<15)"] -[[package]] -name = "smmap" -version = "5.0.2" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -5474,12 +5229,11 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] -markers = {docs = "python_version < \"3.10\""} [[package]] name = "typing-inspection" @@ -5544,7 +5298,7 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, @@ -5562,7 +5316,7 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, @@ -5611,49 +5365,6 @@ files = [ [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -[[package]] -name = "watchdog" -version = "6.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - [[package]] name = "websocket-client" version = "1.8.0" @@ -5927,12 +5638,12 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "docs"] +groups = ["main", "dev"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] -markers = {dev = "python_version < \"3.10\"", docs = "python_version < \"3.10\""} +markers = {dev = "python_version < \"3.10\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] @@ -5945,4 +5656,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">3.9.1,<3.13" -content-hash = "c2fb8567f1a6be319ae73f8c3a30e7b5be6f6fc65deee567d2a9e09eadd984c9" +content-hash = "ea79d82b4e255ec4604f440a507da6dac38b57af93356761ac793678aa615cf5" diff --git a/pyproject.toml b/pyproject.toml index 9c626c3153..7b7934011c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,15 +116,6 @@ pytest-xdist = "3.6.1" safety = "3.2.9" vulture = "2.14" -[tool.poetry.group.docs] -optional = true - -[tool.poetry.group.docs.dependencies] -mkdocs = "1.6.1" -mkdocs-git-revision-date-localized-plugin = "1.4.1" -mkdocs-material = "9.6.5" -mkdocs-material-extensions = "1.3.1" - [tool.poetry-version-plugin] source = "init" From 928c556721d1669036f867c7801a4cc428cb7f49 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:26:57 +0200 Subject: [PATCH 04/57] fix: Mutelist view blinks at opening (#8932) --- ui/CHANGELOG.md | 1 + ui/app/(prowler)/providers/page.tsx | 52 +++++++++---------- .../muted-findings-config-button.tsx | 27 ++++++++++ ui/components/ui/sidebar/menu.tsx | 4 +- ui/lib/menu-list.ts | 25 +++++++-- ui/store/ui/store.ts | 12 ++++- ui/types/components.ts | 4 +- 7 files changed, 91 insertions(+), 34 deletions(-) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 2ef5f2d74d..72c660c788 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to the **Prowler UI** are documented in this file. - ThreatScore for each pillar in Prowler ThreatScore specific view [(#8582)](https://github.com/prowler-cloud/prowler/pull/8582) - Remove maxTokens model param for GPT-5 models [(#8843)](https://github.com/prowler-cloud/prowler/pull/8843) - MITRE ATTACK compliance view now shows all requirements in charts [(#8886)](https://github.com/prowler-cloud/prowler/pull/8886) +- Mutelist menu item now doesn't blink [(#8932)](https://github.com/prowler-cloud/prowler/pull/8932) --- diff --git a/ui/app/(prowler)/providers/page.tsx b/ui/app/(prowler)/providers/page.tsx index 6d3ca01338..e6726c2fd5 100644 --- a/ui/app/(prowler)/providers/page.tsx +++ b/ui/app/(prowler)/providers/page.tsx @@ -28,31 +28,36 @@ export default async function Providers({ - -