From ee129cfe349c22cf2128a9ba55af19730e85904c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 18:19:21 +0000 Subject: [PATCH 01/14] Fix: Improve error handling and reporting Co-authored-by: s9zqh6k6nr --- reports/scan_example.com.md | 76 ++++++++++++++++++ scanner/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 127 bytes .../__pycache__/active_tester.cpython-313.pyc | Bin 0 -> 3046 bytes scanner/__pycache__/anti_bot.cpython-313.pyc | Bin 0 -> 800 bytes .../__pycache__/async_tools.cpython-313.pyc | Bin 0 -> 4627 bytes .../__pycache__/auth_scanner.cpython-313.pyc | Bin 0 -> 1315 bytes scanner/__pycache__/core.cpython-313.pyc | Bin 0 -> 10853 bytes scanner/__pycache__/dir_brute.cpython-313.pyc | Bin 0 -> 661 bytes scanner/__pycache__/extract.cpython-313.pyc | Bin 0 -> 1932 bytes scanner/__pycache__/headers.cpython-313.pyc | Bin 0 -> 787 bytes .../__pycache__/heuristics.cpython-313.pyc | Bin 0 -> 682 bytes scanner/__pycache__/ip_tools.cpython-313.pyc | Bin 0 -> 1324 bytes scanner/__pycache__/profiling.cpython-313.pyc | Bin 0 -> 5956 bytes scanner/__pycache__/recon.cpython-313.pyc | Bin 0 -> 3258 bytes scanner/__pycache__/report.cpython-313.pyc | Bin 0 -> 2660 bytes .../__pycache__/smart_detect.cpython-313.pyc | Bin 0 -> 2180 bytes scanner/__pycache__/spider.cpython-313.pyc | Bin 0 -> 1415 bytes .../__pycache__/ssl_analyzer.cpython-313.pyc | Bin 0 -> 1782 bytes .../stack_fingerprint.cpython-313.pyc | Bin 0 -> 1272 bytes .../__pycache__/subdomains.cpython-313.pyc | Bin 0 -> 552 bytes .../__pycache__/virustotal.cpython-313.pyc | Bin 0 -> 1169 bytes .../__pycache__/vuln_check.cpython-313.pyc | Bin 0 -> 686 bytes scanner/async_tools.py | 2 +- scanner/core.py | 10 ++- scanner/extract.py | 2 +- scanner/headers.py | 2 +- scanner/profiling.py | 2 +- scanner/report.py | 26 ++++-- templates/report_template.html | 30 ++++--- templates/report_template.md | 12 +++ 30 files changed, 137 insertions(+), 25 deletions(-) create mode 100644 reports/scan_example.com.md create mode 100644 scanner/__pycache__/__init__.cpython-313.pyc create mode 100644 scanner/__pycache__/active_tester.cpython-313.pyc create mode 100644 scanner/__pycache__/anti_bot.cpython-313.pyc create mode 100644 scanner/__pycache__/async_tools.cpython-313.pyc create mode 100644 scanner/__pycache__/auth_scanner.cpython-313.pyc create mode 100644 scanner/__pycache__/core.cpython-313.pyc create mode 100644 scanner/__pycache__/dir_brute.cpython-313.pyc create mode 100644 scanner/__pycache__/extract.cpython-313.pyc create mode 100644 scanner/__pycache__/headers.cpython-313.pyc create mode 100644 scanner/__pycache__/heuristics.cpython-313.pyc create mode 100644 scanner/__pycache__/ip_tools.cpython-313.pyc create mode 100644 scanner/__pycache__/profiling.cpython-313.pyc create mode 100644 scanner/__pycache__/recon.cpython-313.pyc create mode 100644 scanner/__pycache__/report.cpython-313.pyc create mode 100644 scanner/__pycache__/smart_detect.cpython-313.pyc create mode 100644 scanner/__pycache__/spider.cpython-313.pyc create mode 100644 scanner/__pycache__/ssl_analyzer.cpython-313.pyc create mode 100644 scanner/__pycache__/stack_fingerprint.cpython-313.pyc create mode 100644 scanner/__pycache__/subdomains.cpython-313.pyc create mode 100644 scanner/__pycache__/virustotal.cpython-313.pyc create mode 100644 scanner/__pycache__/vuln_check.cpython-313.pyc diff --git a/reports/scan_example.com.md b/reports/scan_example.com.md new file mode 100644 index 0000000..3cf9bd7 --- /dev/null +++ b/reports/scan_example.com.md @@ -0,0 +1,76 @@ +# 🛡️ WebSecScan Report + +**Target:** https://example.com +**Scan Time:** 2025-10-27 18:18:22.048748 + +--- + +## 🔐 SSL Certificate + +- **issuer:** ((('countryName', 'US'),), (('organizationName', 'DigiCert Inc'),), (('commonName', 'DigiCert Global G3 TLS ECC SHA384 2020 CA1'),)) + +- **subject:** ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Los Angeles'),), (('organizationName', 'Internet Corporation for Assigned Names and Numbers'),), (('commonName', '*.example.com'),)) + +- **valid_from:** Jan 15 00:00:00 2025 GMT + +- **valid_until:** Jan 15 23:59:59 2026 GMT + + +--- + +## 🧱 WAF Detection +**WAF:** None detected + +--- + +## 📑 Security Headers + +- **Content-Security-Policy:** Missing + +- **X-Content-Type-Options:** Missing + +- **Strict-Transport-Security:** Missing + +- **X-Frame-Options:** Missing + + +--- + +## 🧬 Tech Stack + +- None detected + + +--- + +## 📌 CVE Matches + +- None + + +--- + +## 📁 Sensitive Paths Found + +- None + + +--- + +## 📊 Risk Score +**Score:** 25/100 + +- ⚠️ Missing headers: Content-Security-Policy, X-Content-Type-Options, Strict-Transport-Security, X-Frame-Options + +- ⚠️ No WAF detected + + +--- + +## 🔗 Links Analyzed + +### https://iana.org/domains/example + +- **IP:** 192.0.43.8 +- **WHOIS:** +- **Heuristics:** diff --git a/scanner/__pycache__/__init__.cpython-313.pyc b/scanner/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71894968a8954e098861c8789087eeea4bc1a8e8 GIT binary patch literal 127 zcmey&%ge<81fMtl&j8VnK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNn&`sMjW*~JBk z$*KCq$%%P+sYUwn@tJv7$2D#85xV1fh+*Y C`5Z+6 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/active_tester.cpython-313.pyc b/scanner/__pycache__/active_tester.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e853682249b92d2299f5e1a50f40d2ac3f027bb1 GIT binary patch literal 3046 zcmai$T})fa6@cgd`TrV-ZH)Of%a3sgW)Tn|0aigkvV_0*;>cMmLJq!8+_kTrxn>i1 z;-L?2_aUU}3bU&UR$ZxV)dwo|A??#8$EV2l;GUK;BKouG%-AQ;)6@1<0JLJY5_W6{;P zkX%i98P%|ga4eM`564BE?&|Z7_Lc_Lov)3L z%J1CnO-k`(I5MV97+*Jg+zWoUr_VFy>07V$Ty#(R7Z%-1mnLSX+^e#P<%>N%D^%&6 zq%p$Bz<2D1)4zv$kwk^c`T!a%$r2r){o4JIb*0i(Y9pWvQ0*vy{G6t%v;w8s5on^8 zn2=u6o^JP4D~x8vhOV_spcu7Q29r>&6l6{3d7J@A1(0(yOb^Ycah$$GHVkqp860i6eW0mcfk z6spo@1q|9=tf-%njLj%it?B${G^x_Qh5|+sT_dz)3a`uq3ToNH4>gG0}-tnu^5euD`_#}wP2Q5;O?%9 zcunQxR6Lee*+^_9ty+SDu)KKb#_YnSNkQd>YqM`wy6j3awT8(Lss=2=YB?>d%xw`C zr^WkeRToaBL@DAmV~7Hz>ai%V#$k%CkYf>*y%mO3qf&)|a01r8Ev{j5^Qt)w#+D2F zmQ_QcLpDue*$irTrKoMa_mcQ+ITcwAxJ;Ib*q2&U?F9?>WqDb? z8;>o=q-C&%Nhpwrl1-3R8%VKMZ(Kg)9h)of-rSg0%ry^2AB-whwHfQzX6u8IpNwn@ z?~Z3%bLPg4$wR*8lg=mS9-n(Yv6P!wItT=xP29+zdF$!KjYrpZ`}bOZIlS4oHS};O z+yC>?{nmZ|zVoSVI5%-4cjm1;AKIAyrpl3F4xMcuC7wC^GnbWm_fGeA_Xj{|!^*+6tI@RK^q}4Gb2`X=s9^ zfus{?NE$j+h7#aLr>&~A0y9{Go5TbdKY`*aMJOkZ1gHT3`~Uy|RQSoRUMT{nrS}+h z5|D1#k)-=CYaqR~J@`q?y;90hA<#K(e`TadhLA;D9I|Rs30pg`Yh%Cw05!?<>yYMf zl5+?pe|_pXjM_O)o?@;ZFk|}P^8?0Zdd^owie#2dA(D`V64Ys@lq}6CpchORf`IK> zbkdurA~5(z!7Nxt7>QqS{6EhZ=JI52@E51~yMRjoCE(Jb?X1!QE{%d!sPdH`9DpU< z+}wx76J4g}Ac4ulaRQg7GA@6o;j)NNGN**k>iHMs9l_+$SU2BEC*r;Wny|`7lQ;nY z1CS&oRhJOccajkdHw~DKNvTypoD@!oDw|5mX-xdxDGW?NIIr3{VK1+tXNd*ywR9#`;a*?l848L9&gbfp5Gr(qOl zsrEnUS^R+uplpLr9*65M@$-mwar~jh{$Tc}vsv5jt(?W1p$@AWv)6X6Z(rZNnXl^4 zuu8Rki+{*xqxtHN4EL4Ym6=o=uC3t1VD?Vl;d$=x?e%A-%R_ry-r;-h7|1yW@{aRG z#m$E|cZc$h?#%RIgXcFr&l-j{^?w>3&pQ|Ypxf_z(($l5vcC6c0rMpk@_!Mu?OFi3F{~cmo4ZzmA?!VQb z#^IMncJeUtS;r;El!g8Lf*Cqr=s8kb=&2h07gimS?evt3DFu_t+>!?h!K7N?jf=-_ zl|q_4@rAd#7(|trBCjAOuKXAsLyd|)H;g8dk=3|3j^BVW@Gp}4pgLkGiuwvw{TVg= q1r6rV;MdjN2R+jVRhKs`N=-9)rK(3Z_-|PkWjj9KNZIf>toj!TUU@D6 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/anti_bot.cpython-313.pyc b/scanner/__pycache__/anti_bot.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..208ddedab09a68867dcb099348d5d8e516e7f8d2 GIT binary patch literal 800 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 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/core.cpython-313.pyc b/scanner/__pycache__/core.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de77e57c700191a205d0aeb29c3fd49069eaf805 GIT binary patch literal 10853 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 I3{?340pRBe9RL6T literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/dir_brute.cpython-313.pyc b/scanner/__pycache__/dir_brute.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef64984117b1635c71c559585150761862281414 GIT binary patch literal 661 zcmZ8fO=uHA6n?XlO%t>EX<|gAF^TpPum%qz{z4E@&=is-P*7O5$)pW!cGsC*#6uy+ z_9P0m^`u9=>CL0wyoO%F)SIW=QuZQV^i6C~`oX;U-kWdUd-D?}lcxX?e)_$Bi2(di z&J5Zy3A0Fc!32{$1P-Ql4mepO6*^Xe<@QXcyeJNeBm9PD7pmZBEs}!=4FHFphsPNG zi{}5kj$;>~1xRmT*I+A#jaG^?+ z6lAgb%;g)xv)imH+P32`UhVX_waNpZUG|>K?37{pe&2PhX)cbZo!-9aA{oHQNDb~C zCbQdQqUrJc_srQnm5Bv zUmRufd&Wy+zwtVKL?N?yGzY2NBn8I&;L*>CPU3lKoc<*YEVWZh)E2a_3tFwLS2P~O r(6pBi5F!PxK9G#v>bsp_lilHG&{YpAl4vHB5ON6VFcK&8Jdcq-8%mA? literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/extract.cpython-313.pyc b/scanner/__pycache__/extract.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bb2a5dc9818b4535e15f16d808edbc6bd3414a1 GIT binary patch literal 1932 zcmZt{OKcNY@V))mCXVAc2_RA+CZuXyVJF}KO$t^?RTU8tiENgXZXw;+yUv2MYu;P8 zBoR_XFGf93sYD9m0Jj3C9xBvc+7hV}<>X>$(1WUn_QDO}3$%xJ-r8$G?UQ!i%)FU- zGjBdyi$q!wteZFgnf*yX=nqc#3utfEhG1Jp3R3VGI)n*6BoHCaX<6k^I{_Km>Wn4EHY~7CD$@b1;r^9nK-HE6PN`{q;S%)?C~u2E~1 z&H;BBZADu1aKd+~$w8@xk9R_6KW8K^+`CB zW{F`EUQCHr79z&!l0jX{f{H<@<=9Nj8!q8#Ws+;0aaqW;?A)|oD6kfbS~hicJ8KXL z1PH&4t$XiG)+oB3Dm7dtQkNuxOoWnQLE11X5rY<)M7^4F^*oacmVJscY*3!*czv-g z1D`tN6fNpmV}NFL+cwAm_(1fmn<~z;NCThtl*wk$y{0+USnA@&mCh^qOZltjW#`ve3Ixk?rM>td+Vw>a zOozIkKNV5zG=BDHxT}URI1=snymT?Uvh7Q=eD;25^WRTI;QmFSG|;&Nk*7 zyL4;ER)DK5GH_PgJ-EJQG$XF|bd3&+t7**fVHw~ny~p3b-g|(YKqo|Kd#s=_G$#%~ zyC1M@R9(nLd;4-3$PKJshS*Xu)x zz&POznGhF(ALSNz4(>R+5YhDH!hz(6#}{{TvQ-;Q4(mzt_+on3S&(bZ6Me=q=TkhH z3$0eM;NSy z&WlgbvK&W;H`O~dkM@ctzK+MCF#^=kNp?0NF(Ea`aFGebAYEXD^2d(HmzKzDfUtG@ z@`i1kDUuB9%Neebb*HW3wD0e}f|Jz?G?Qvdc_yIC(X+=;rIohyjsB}g%H+cQ`T3RD z%DZdPcqJOY(f{1L=Cht&{7S$)d0xOf0LkE&0*3MH#3qHef!_!U#vmU%^`Hp9@;YRM zYy*VhTs;rQ*8zAwb^=oOaYVf{bsPUEe*e~;sfJ&%E}Rg^b`W8rZqFwq z&m0RCb=NhB4Fe?S%;_*6JQ**RkK^%o>Q5H?&pQzwPCI;bCxGWm=%Ey0(ah38)|Od% z|53Q@ep~u_f3+=L4X4+_>NoV>i2CzXbwsW1RV$KO10|FQF>!_tdBe&ZNURlxPQjXK z3_M;dg#Q}6N#Wf{-r!`C1231MeREE(R4_7R5O}%B-QP literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/headers.cpython-313.pyc b/scanner/__pycache__/headers.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..074f04a1b4e628bb815298e94215d51c16b9be81 GIT binary patch literal 787 zcmZ{izi$&U6vu5}em1E>sh|RZ6cvV^sv}egsS;PDs8xRq3AERsz!F&(H#sG_3-*O5 zSB8uY3}tDR6(+g?1Ahm!6JGfTpqZH9IccB_ebRgPY`^dG`C|_zCnpft{^#G-LyXXO zH5eO=hc>tbp95r}>xd%@L&O!LYxa0u6~(|c@WC?(2Pgw|o7lw#JdK_ZLGB_!rqTKp zguK}T+B&a@~x93dRHqGPF5 zn6GOxT4l7%xR7zDp~TenC9Vo}+;loEZ)VyauZbqLSw`5@bs5i`Deg2oGDTfl?*y!L zHpk<7X1xGfxucC;k2AUVDGHx7%OnJ142RlBILvkVh)Cm6rs3`;L99(@dT_9l?1M;a0kdwR~mJy5b7mNpC zAiRmBv-GQ;O62yu|wniJ&If8rQs;i8(=}xJ}Izr;w7#LS?MNX8N z&uqU01swhhsb?tWA#{;Ut##MG-I@m)?-u*?!`jiy*SWpyo9DgYRqpNLITor@FFqSO x1kdYah<4bGCt+m(jz+u1%!XHP)!9Q1ryS8&oBq%+#-}8Tmj;&!UgGMZ{{dO!yiWiC literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/heuristics.cpython-313.pyc b/scanner/__pycache__/heuristics.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..debe8a6ca59e9ca1564368880ce6e50cbbd9e398 GIT binary patch literal 682 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 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/recon.cpython-313.pyc b/scanner/__pycache__/recon.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2b97a26ccf9a0539c9b82207f71cadae65ad6ce GIT binary patch literal 3258 zcmcguO>7&-6`uVgSLVu;Ov~weUA<%X{{bBKcE<(OSquIoUvhyY=cZo(cZiZ0&@-y5tPx)zq z3K6u08F5;oQiKG_5YfaC(WKLSeKvJzt`MPa%?(ms;8b}F2jVJkyNfjYZa$Ntd=hpQuQEp*@#GXqF~=8e;}082<0`73YuFJLgZbJinuzJG+C1z z8&;?81zzp4JzC1hX(nTtd~OR;CGnKd79EpCd*>R5!7sOl63t~>Jp__G-LR4*+ZrDu zV6|c4l@t@6XZ@b8mcX6wYDwXFbB=&_l5g$h z09w!UPm)e+Xo3@mB$}XT+e0V+M0cA*L+u>-G@LiK2w5Pi7`M6D7WUY#*Yf!*8B=xJ zGBw{Tn#^LhyYc^QH>>|h+r4NSX_GQrVuftRf{#Tr1>03Dqz%hdC0k@>cHZ^?rWWs7=6kck7J% zBzKBK)Q)jUeu_K89VV&}x7}7|$;=n6Ei5J~PrdN5g$2`61?qQnV7TqNa@C* zk&z%ya+9F^^5l|GPc%NtCxupebPyszy%GUZ20iE)^%TWIo>{8s&!B& zOiDK_pwL{=GMTR5C(g3SK`@_J{e0xjw+YFKP=`BIux1@jAi#GuxbhlVHEV4e@_0*yU5JU^UCv-?F3sJ{ zTyx_AC4A3%HCN0@P__G>9Z$AajgYr=M1BP7`(g3|dGQ9Oz|Q#}ZhBGXg(v}uc$dCju- zIF%Q11yva4n-sc$C>Fb#u@)(&P7y4&%P17gTv~P6@|?j;9c#j#`j+Z?a|dSH7OBY! zwun(}v0zw>41YAx37UgOyJz0CQj2P*NEWKRmJ+VAp>-8;9 zZ}YO${n#70ed8}TR;M3uW$*Y+vF7nsJ>jw^yviz`p;~ZYy=$$j8XPGHM;-;ED`L$T zxEH$<``h`d?`YX~bmLsrcedg?TMG}a$JSz{)W(TQ`1poV4v$sCXUgF-mGH#MRLy^| z>K`uqhfC>4{$o$vqZ;9A7k7lM}$J5#SY~xC3{RQm6QxSkvS~ zGcKSWUV}f2dUyP~GRiWRxnvZg*Osz$6!uTYP@Di^3(#i^*?dav+J$sFj!wuDdJ;Y5 z`AjaY13~Cn>_b4SE;@ljp2h+Wh$VpR6-LkDfXLtpOD}->40?Ta^fqVKCtU&dF^{>8EidXiXT%M^ZdzW9|=G{*LJoBKp>^->~z>j^AlK+$bkNY=XulUB{IS~lo z`{2$8rR1Z)u@#}#(|1p~qpWJ{m)0(=zq9sE<-qYrJ<%ru365;bMCo0gZNq2J^6d9a zfvv1uI6t|Ue6g3G^a*fKSPg6i*qhDFMV)th{p|3@vxPZ!s(x(Fb%C6um=`^c0&nX& zedY7@i|Q5nJLm&k6LuKHw!m@Rzlrz1 zL*RrWRi(KE4iOxB@DXk~MZJcR>!TL)V>go48I6J3ig_ zrJcpbr03(@E!VU#nF{IMFvw?3AA50*O4*V*9Vnkvk`O!M;lGuI^Piw2Xn4n&*gdae zO^7@09VN+CW52?ks8}snt3lpQVAp=^tUav*B?Csl;Clla8kVchM5wCOdPAt5;_yj6 z5w3ZvS`ryzFXsru4LcUjkc^-q_woVch)FRKsd{RCa%W$NxAg0&!ndIe*N%o8tGfk}31>T!p(LexbUxq(+F(aVx+cXceTK`R^18S>NI89I ziK8-pV0(Vb9h+(*IPVfaRefptsGrNi48cpA4&^4c#e$zXeX+F0NxPQqj7&A%9J+l= z!w=X(En@-%tV`OGN&~J(g}mvH(x7D%$IM|W=gct-1CR398K-q4xONnk2zCH7RB-dy zq2dIweN2_>57IdA+pe<|qza27WtqN7LvF#(7yMLdf3-4zovB<6oyz6?utm8H<**lo zhoqGoJ=8tnk})rDrg1mh)WM{?oVuQ$q>bfdQY`sY*-T=vnq4?=9>g?y7YK=6Z<<@? z@66qqPtB!@!)wiFUo~HRYRsscvidmmC^Xx@*t9C^Z=(BOMLS_>8Y>O)bvgd=9S^4Mt;-PG!)vGdTG4a8u}|Tbkqr8>qV}}} zm!mw>TS~H3?K{se$AmteUp~PxT^B+Ae=*?!?zB&MF$n|f5MlLbs&Vg~%vppXk<>B4 z$`Gk%^A6lL$(ZF%ID79R*~jJ_1hM-LkWMhreHEzZtSZotgZTiPTLZfOZ-IVsE!z7Z z0bSpS#24Eaqbm)k*X7eCpzGV@CVv%~NkxxD)hbRz9sUFhoNt)qZ}s z#MaD_VDD{D>pIP}r}=0W~%;)Fj8MdX@Iv<+K8|FCH`i-v# zwKAAA-_`t4tmTP2Zd=&WSRK_>^{b3_JOdS+RqNxL?WD5>3ws(AR-E=-Vq@08Ea>{GyoV(<8a1qR?>fB05oAzCc1 zxJ(#J5=Lqy%0v?r43kv2@3{^YtU@kN#XPYcKQ6$KF@ju;n;c=&+d+8jJBU^M3f4V1 zB30s$N>M)p{$p(7JrL9A4;3{w&9}_8%(u<86%VXMx?V-jE~cmZHwEdtfrkULYH?^) z=-O1mGouT9ab#7|pSEwxb&r)t%51FIzADF{WUJk81ZC#-!ij~}6*aal9EI|&cCRb? zw#VN5b7lXv3;gp7YM&5{tL0Qrf}KkDZRfsujxZP#7CCLGa#^bU-9(z1E5-!7rG$mQ zgtkDUa)$EdoNE=b_zThlHn<7MI}ZZbf#Y5y4u-A$U@r- aDL&1=m+H8#_lN2@ee00I9V4H?y1xLf{w0S1 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/smart_detect.cpython-313.pyc b/scanner/__pycache__/smart_detect.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efec3a4322841eddcb0a9b51b82c691ab461a2a3 GIT binary patch literal 2180 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 H%*g)__u@JK literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/ssl_analyzer.cpython-313.pyc b/scanner/__pycache__/ssl_analyzer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cc65ecbc13e8b51004a353929f2501321340339 GIT binary patch literal 1782 zcmah}PfQe782{$q?kvjyyMTaNSe8GsjRjn56sQ#}T(r@2$4#1W*o?bxVe2xpeKV^_ zV%qeC?ZIA5@Fbp~H=6d=OQU${30N(gr%jvGOK&!7Vxoukd&3Mh($;UX@B8!n{=N6U zeKk$11Ekuwf6XRkfZw^|ljsSxeiUwk4!Up_=nfaCfVy~AJR?yl2D-E#s5~Pi5@lpM<~KPni8Cbi(v5nucSZYzk>15m(Yms(CNITQQIhq+0jly5e{LA9&ymc<>Fl>f?H7 zP_{*!2mFqC_ZEY_mR{(F!{Fay4sSaz#-Iyg_??$dN?ni_I^8U<=9&4Qs2SLa>V{WN zec#@*2!as=tfJTH;bFlRyPy}Q+kHPo_v1VS5v`v-;9JwX`)`1w;!c!~UK7Dy?2+@l zkoW2*v~x@trF`#&A8=nd|BW`GmkG_xTDD^vbA$y7wp}Ka1?}?1Pe|5bA=7eBle|R< zQ&F7AJA@`=ij4y0^T_Hh8^uB{lc&}k3-8Ef(<7h0=K=~h!1x(@;tt?aQQnBE$fNf;ib%Ti)EB$Fd~qg-?{S<7_Dyu%`%9d#2of~8sb3N=a@FIO0Il?b6(LLDZd!qgd~ z;P%WyQ}bC;a$_Xhp$U~~Icv@+m>G*^@X25S_tD5PnRmm2eodL@#-?2+yO{-I^J9v6 z#NtC&EPBZ<8Cf!9XASI)4&gR3?olidI#{~O!k&^rfc9cEe+le9yt2>Xso0^6J&ANI zy;F${EC#Bv{uOOSTfBVZ+LzbXPA;!_}^y+w-^PS3{MqWHsKuGH_>Lb*vIU zUhO%wa^lX3)iafz<9~*f_ULm3q7%YKWc;}V4M#V%yQNhSsGZTGqoVTGm_YCgZ{N zPKnELckOGRic9OKLb%>n8>ilq?zahCeoMhMq0lJSp-s53dK`*k;o_h>7VZeU_x26c t5Cz)9ebo2ntX#QB-lzO=!OH}jKbY9FAUuM)N1*)z-8Ff?Fh&od{cn6qeu4l1 literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/stack_fingerprint.cpython-313.pyc b/scanner/__pycache__/stack_fingerprint.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fce76e60b0fd9b45ab5bd614b7add3065ab5841 GIT binary patch literal 1272 zcmb7E%TE(Q7@ytlcDsG?5Gp0D;VJY$F$y6@(S-0Y3eqrXLV`&%wnIxF+h(>r+Eevn z@j?kP^ypEK{wcKwHsggvFWyM%8J+1XNFd^u?0k>;{eJV!x0|gw99F=r_T_iXW&+?l zPg*86gIYHWJ^%=y@B$D3#eP79sNggG>=> zO{QkL24lK_k=E}+X^hFN&q0}cXbwti{H#kh-OL{`kIW3G1Vw1!jMWvW-jHK$a~?q3 z4LSBU#|<4f;K-~uXN$AJCs(&M*7Ic>0m@ZqOm;A#N>EE^>0K>vU!Mr1cQL_{iMbp#>y2&uUMSP3hF-I3 zB#yLbnwsYuUfm=pg?G~=L6syTXa^^$vfK>h%_PzjR9>t*1@C1D*7dw~p%M7wdR~6z z&%`p+6nGY(mL!wjMp2xXSe2a>$Yo<`jc0M4TA!mG1jA%2ll4i|6vbI8CwXI3B$#)L zNI2$`sS?8|f{9M0ST>cUvYtiZ1mW+ED$R@NY#v~M8y%bEz3Uki#*=y&X&NSzbw+4& zON(NXAzNCOH{-gHhq5>v3pIqjOp1T#`Z-$)z_2?D3x=cTU}Jxy-1qTe)iF_6G@QK$ ziTy;`|H)T%P8F66m!~-XX4UWv7yT8OJyd&-U-*_LdS1kQM6!l&1ZLZW;o;pzmD$0Q2 z8Dv}Wip70q2llSQ@-G{(+7I0C-KClG{E0ePQ3nkVm#t$g4UBRHX1s|xwRIMjYit6* xClVKPgwN$OAa{7|VGFf9PNgH;N&JKiu$2FGbO&3`OoAYM18PmWEp*l4e*mH67Z?Bl literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/subdomains.cpython-313.pyc b/scanner/__pycache__/subdomains.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22dd65baa666b40834cc8e1be41b4627beccef2d GIT binary patch literal 552 zcmZ9IziSjh6vyAp>>gR2-Q>r}qIQi&=mkF$A#8 zq%#jMkU7)(WsmdO9f+NWxRX@~F@U3B+l(if(Z3sUE}m# zW>VWlLMszC5!=y&Bo$4aCQ=I3MDEN(FAet1^{yHgVxyOg6m4}~r__qF;FHU&%u>HDyT>rx7e|j)? za~c4zj@rMbF7XgU;TSFETiw)U^J SolhAd293aG`1pm zto2Y-#E61kJ&AvUJ@vSy2w@ZyJb4px5k2TPX+lMOe23rn*x&b<`DW{FZD9mbfBhrR zB!oT*rzLXJsGk7i5i*g9#}M7nnBpsVLTOPq^zt&dE(O9 ziL^O+esbhoX5_-z%-HG6c9Y%62>^<|^=lA)gr*un0sT!Y3q4J2N<+98?Lnrz$r!>8 zo`N%M$X{kt(%4kfXb78uVcC(=(uC;vo0!3)6@;vq==5iX=nh{(za@fuxEV4v6)ZC( z(2;+ct8w2=Qd7a5h0}5j9g<+W$gt!nY2{zFdr=RXineM5k<|q~R1;6dTd}m-+QATJ zQa9|(QAgC?rk=*r=(yaCrm-F!=PIGpr3>M4Hw7C>&5-#8?dE`0DhR7D?|CJ5)G#WQ z%HUjqmYL^zR&mgFXAPhjbBByFEix_x>Jks;iIpXkaV2Yc*5_52hiU*^Dr6T9{=b&x zSjBlpn4x1Dggjm2!R9mJ8YMT%gn3ZBug}(c_pfK}j`)r;LhAXIROyi6NK{p~f#KFU|9I zJ5TJH%w~{ohjdYby#ly{K132rM{4a|)ubQq^R>P&Qb_Hob?kmt@e}(OgYRS8mS>k{ z{p6|DoPYJ2pUwGK@@uieqFjr_mQUV2S+&zK?X) z5rzrcyPkDaFFxJo$5U^$)b}qk1i!I;F!TN6qiyI-T;NJucyv%+>57i(^2$CeY+VJ^ zRVv;bmt4jJaM8q>qY(%+4*wF>!9)~ZBI4SySvOlQk`r_{xZ?L_-2l)I<98_j4uw93 Xqs#h|?sxXDhxI$bx)Q}bGzpxaeCY|` literal 0 HcmV?d00001 diff --git a/scanner/__pycache__/vuln_check.cpython-313.pyc b/scanner/__pycache__/vuln_check.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b46fceef1088c176f7700393bc4458224a691aec GIT binary patch literal 686 zcmZ8f&ubGw6n?Wm^22P*)+RxO+Qb%Q1+#h({85Eq3k6NZEP;TBWxLrVOOn}jX4BR{ zA;)?UMJeh@k9w>Bf!@59Uc%Iyr`%F<5idF$(ja~??|tuk^X40v87GrxfMfjSPjd$W z{0xgz2vagX$H_hzV4w#;zz{9~K@Fsd!*Iif&F5x_a3=CKKHy;=bUyPKVRQ|!P(kys zUIf76MR+nBD4)8_6o5-mfwKIMFr|iYW9n5AuX4>04e6GErHWJzegA_Y->w2!ykkVF zOP6`K#NA8x0TPgdim(iMo)@9?5a6!32z8`IH#Dgi(F+YX(0ehxVAVP<)_aQXH+>h^ zy#mi+-@#itL;5hhqlQ>Ca669I4Tj%&&7x~NzDJ#!Cb1ZCo^~A?P$o8dW2dyC21))Y2e-*%J}e z<&!APWk)I~>Hec%BN@drOiBNy32yV^PEi%UsX{R$YXXULWpa(rFy<6U__J7SrQ_AQ cZRZ}z@-lp3n&e?5B7}}1HI@=6O)^~h3x^M$Z~y=R literal 0 HcmV?d00001 diff --git a/scanner/async_tools.py b/scanner/async_tools.py index dd191b3..1dac8ce 100644 --- a/scanner/async_tools.py +++ b/scanner/async_tools.py @@ -52,7 +52,7 @@ def load_plugins(plugin_folder="plugins"): spec.loader.exec_module(mod) if hasattr(mod, "run_plugin"): plugins.append(mod) - return plugins + return plugins def run_plugins(plugins, url, html, headers): results = [] diff --git a/scanner/core.py b/scanner/core.py index 1b5034e..10acb75 100644 --- a/scanner/core.py +++ b/scanner/core.py @@ -140,9 +140,9 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json", soup = BeautifulSoup(resp.text, 'html.parser') for script in soup.find_all('script'): if script.string: - js_snippets.appeend(script.string) + js_snippets.append(script.string) report["suspicious_analysis"] = is_url_suspicious(link, js_snippets) - except: + except Exception: report["suspicious_analysis"] = {"error": "Failed to analyze scripts"} @@ -157,10 +157,12 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json", report["ip"] = ip report["host_profile"] = get_host_profile(ip) - except: + except Exception: report["ip"] = None report["host_profile"] = {"error": "Could not resolve IP"} - scan_summary["reports"].append(report) + + # Always record the report for this link + scan_summary["reports"].append(report) # Final risk score scan_summary["risk"] = calculate_risk_score(scan_summary) diff --git a/scanner/extract.py b/scanner/extract.py index 68dfb28..abb2ea8 100644 --- a/scanner/extract.py +++ b/scanner/extract.py @@ -16,5 +16,5 @@ def extract_links(url): def detect_ip_grabber(url): import re patterns = [r'\d{10,}', r'[a-zA-Z]{20,}', r'[0-9a-f]{32}'] - domains = ["grabify.link", "iplogger.org", "ipgrabber.io", "grabify.link"] + domains = ["grabify.link", "iplogger.org", "ipgrabber.io"] return any(re.search(p, url) for p in patterns) or any(d in url for d in domains) diff --git a/scanner/headers.py b/scanner/headers.py index f01aab3..ad9a3e7 100644 --- a/scanner/headers.py +++ b/scanner/headers.py @@ -8,5 +8,5 @@ def analyze_headers(headers): return {h: headers.get(h, "Missing") for h in important} def detect_waf(headers): - waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuro-ID", "X-Akamai"] + waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuri-ID", "X-Akamai"] return {k: headers[k] for k in waf_keys if k in headers} diff --git a/scanner/profiling.py b/scanner/profiling.py index 0f3dc9f..f2e9785 100644 --- a/scanner/profiling.py +++ b/scanner/profiling.py @@ -99,7 +99,7 @@ def calculate_risk_score(scan_summary): if missing: penalties.append(f"Missing headers: {', '.join(missing)}") - if scan_summary.get("waf") in ["None", "", None]: + if not scan_summary.get("waf"): score += 5 penalties.append("No WAF detected") diff --git a/scanner/report.py b/scanner/report.py index b24d291..5fefbb3 100644 --- a/scanner/report.py +++ b/scanner/report.py @@ -2,26 +2,31 @@ import json from jinja2 import Environment, FileSystemLoader -template_loader = FileSystemLoader('./templates') -env = Environment(loader=template_loader) - -# Setup Jinja2 environment +# Setup Jinja2 environment once template_loader = FileSystemLoader("./templates") env = Environment(loader=template_loader) def export_html_report(scan_data, output_path): try: + # Ensure output directory exists + output_dir = os.path.dirname(output_path) or "." + os.makedirs(output_dir, exist_ok=True) + template = env.get_template("report_template.html") rendered = template.render(scan=scan_data) with open(output_path, "w", encoding="utf-8") as f: f.write(rendered) return True except Exception as e: - return f"HTML export failded: {e}" + return f"HTML export failed: {e}" def export_md_report(scan_data, output_path): try: + # Ensure output directory exists + output_dir = os.path.dirname(output_path) or "." + os.makedirs(output_dir, exist_ok=True) + template = env.get_template("report_template.md") rendered = template.render(scan=scan_data) with open(output_path, "w", encoding="utf-8") as f: @@ -31,7 +36,12 @@ def export_md_report(scan_data, output_path): return f"Markdown export failed: {e}" def save_scan_report(data, filename): - os.makedirs("reports", exist_ok=True) - with open(os.path.join("reports", filename), "w") as f: + """Save JSON report to the provided path. + + If 'filename' includes directories, they will be created automatically. + """ + output_dir = os.path.dirname(filename) or "." + os.makedirs(output_dir, exist_ok=True) + with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) - print(f"✅ Saved to reports/{filename}") \ No newline at end of file + print(f"✅ Saved to {filename}") \ No newline at end of file diff --git a/templates/report_template.html b/templates/report_template.html index 9f6b5eb..3542744 100644 --- a/templates/report_template.html +++ b/templates/report_template.html @@ -37,23 +37,35 @@

📑 Security Headers

🧬 Technology Stack

    - {% for tech in scan.tech_stack %} -
  • {{ tech }}
  • - {% endfor %} + {% if scan.tech_stack is defined and scan.tech_stack %} + {% for tech in scan.tech_stack %} +
  • {{ tech }}
  • + {% endfor %} + {% else %} +
  • None detected
  • + {% endif %}

📌 CVE Matches

    - {% for k, v in scan.cve_matches.items() %} -
  • {{ k }}: {{ v | join(", ") }}
  • - {% endfor %} + {% if scan.cve_matches is defined and scan.cve_matches %} + {% for k, v in scan.cve_matches.items() %} +
  • {{ k }}: {{ v | join(", ") }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %}

📁 Sensitive Paths

    - {% for path in scan.dir_brute %} -
  • {{ path }}
  • - {% endfor %} + {% if scan.dir_brute is defined and scan.dir_brute %} + {% for path in scan.dir_brute %} +
  • {{ path }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %}

📊 Risk Score

diff --git a/templates/report_template.md b/templates/report_template.md index a3ef0fd..314ca41 100644 --- a/templates/report_template.md +++ b/templates/report_template.md @@ -25,23 +25,35 @@ --- ## 🧬 Tech Stack +{% if scan.tech_stack is defined and scan.tech_stack %} {% for tech in scan.tech_stack %} - {{ tech }} {% endfor %} +{% else %} +- None detected +{% endif %} --- ## 📌 CVE Matches +{% if scan.cve_matches is defined and scan.cve_matches %} {% for k, v in scan.cve_matches.items() %} - **{{ k }}:** {{ v | join(", ") }} {% endfor %} +{% else %} +- None +{% endif %} --- ## 📁 Sensitive Paths Found +{% if scan.dir_brute is defined and scan.dir_brute %} {% for path in scan.dir_brute %} - `{{ path }}` {% endfor %} +{% else %} +- None +{% endif %} --- From 07b8e225d15aa586e03d336a56884d53a4ab2cda Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 18:28:53 +0000 Subject: [PATCH 02/14] Refactor: Update entry point and add package metadata Co-authored-by: s9zqh6k6nr --- reports/scan_example.com.md | 2 +- setup.py | 2 +- websecscan.egg-info/PKG-INFO | 21 +++++++++++++++++ websecscan.egg-info/SOURCES.txt | 29 ++++++++++++++++++++++++ websecscan.egg-info/dependency_links.txt | 1 + websecscan.egg-info/entry_points.txt | 2 ++ websecscan.egg-info/requires.txt | 7 ++++++ websecscan.egg-info/top_level.txt | 1 + 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 websecscan.egg-info/PKG-INFO create mode 100644 websecscan.egg-info/SOURCES.txt create mode 100644 websecscan.egg-info/dependency_links.txt create mode 100644 websecscan.egg-info/entry_points.txt create mode 100644 websecscan.egg-info/requires.txt create mode 100644 websecscan.egg-info/top_level.txt diff --git a/reports/scan_example.com.md b/reports/scan_example.com.md index 3cf9bd7..4b9ac0d 100644 --- a/reports/scan_example.com.md +++ b/reports/scan_example.com.md @@ -1,7 +1,7 @@ # 🛡️ WebSecScan Report **Target:** https://example.com -**Scan Time:** 2025-10-27 18:18:22.048748 +**Scan Time:** 2025-10-27 18:28:40.472899 --- diff --git a/setup.py b/setup.py index b582985..2244600 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ ], entry_points={ 'console_scripts': [ - 'websecscan = websecscan:main' + 'websecscan = scanner.core:main' ] }, classifiers=[ diff --git a/websecscan.egg-info/PKG-INFO b/websecscan.egg-info/PKG-INFO new file mode 100644 index 0000000..3e14e4c --- /dev/null +++ b/websecscan.egg-info/PKG-INFO @@ -0,0 +1,21 @@ +Metadata-Version: 2.4 +Name: websecscan +Version: 1.0.0 +Summary: Advanced Website Security Scanner +Author: Your Name +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.7 +Requires-Dist: requests +Requires-Dist: aiohttp +Requires-Dist: beautifulsoup4 +Requires-Dist: dnspython +Requires-Dist: python-whois +Requires-Dist: rich +Requires-Dist: Jinja2 +Dynamic: author +Dynamic: classifier +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary diff --git a/websecscan.egg-info/SOURCES.txt b/websecscan.egg-info/SOURCES.txt new file mode 100644 index 0000000..4aa408a --- /dev/null +++ b/websecscan.egg-info/SOURCES.txt @@ -0,0 +1,29 @@ +README.md +setup.py +scanner/__init__.py +scanner/active_tester.py +scanner/anti_bot.py +scanner/async_tools.py +scanner/auth_scanner.py +scanner/core.py +scanner/dir_brute.py +scanner/extract.py +scanner/headers.py +scanner/heuristics.py +scanner/ip_tools.py +scanner/profiling.py +scanner/recon.py +scanner/report.py +scanner/smart_detect.py +scanner/spider.py +scanner/ssl_analyzer.py +scanner/stack_fingerprint.py +scanner/subdomains.py +scanner/virustotal.py +scanner/vuln_check.py +websecscan.egg-info/PKG-INFO +websecscan.egg-info/SOURCES.txt +websecscan.egg-info/dependency_links.txt +websecscan.egg-info/entry_points.txt +websecscan.egg-info/requires.txt +websecscan.egg-info/top_level.txt \ No newline at end of file diff --git a/websecscan.egg-info/dependency_links.txt b/websecscan.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/websecscan.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/websecscan.egg-info/entry_points.txt b/websecscan.egg-info/entry_points.txt new file mode 100644 index 0000000..e463b40 --- /dev/null +++ b/websecscan.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +websecscan = scanner.core:main diff --git a/websecscan.egg-info/requires.txt b/websecscan.egg-info/requires.txt new file mode 100644 index 0000000..6944864 --- /dev/null +++ b/websecscan.egg-info/requires.txt @@ -0,0 +1,7 @@ +requests +aiohttp +beautifulsoup4 +dnspython +python-whois +rich +Jinja2 diff --git a/websecscan.egg-info/top_level.txt b/websecscan.egg-info/top_level.txt new file mode 100644 index 0000000..421ce58 --- /dev/null +++ b/websecscan.egg-info/top_level.txt @@ -0,0 +1 @@ +scanner From 278d0e0f0166ec4010d4bb04d6ee441931975010 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 18:33:06 +0000 Subject: [PATCH 03/14] Checkpoint before follow-up message Co-authored-by: s9zqh6k6nr --- MANIFEST.in | 1 + scanner/report.py | 7 +- scanner/templates/report_template.html | 92 ++++++++++++++++++++++++++ scanner/templates/report_template.md | 77 +++++++++++++++++++++ setup.py | 5 +- 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in create mode 100644 scanner/templates/report_template.html create mode 100644 scanner/templates/report_template.md diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..5e91c3e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include scanner/templates * diff --git a/scanner/report.py b/scanner/report.py index 5fefbb3..fbe8238 100644 --- a/scanner/report.py +++ b/scanner/report.py @@ -1,9 +1,12 @@ import os import json from jinja2 import Environment, FileSystemLoader +from pathlib import Path -# Setup Jinja2 environment once -template_loader = FileSystemLoader("./templates") +# Setup Jinja2 environment once, resolving templates in-package +PACKAGE_DIR = Path(__file__).resolve().parent +DEFAULT_TEMPLATE_DIR = PACKAGE_DIR / "templates" +template_loader = FileSystemLoader(str(DEFAULT_TEMPLATE_DIR)) env = Environment(loader=template_loader) diff --git a/scanner/templates/report_template.html b/scanner/templates/report_template.html new file mode 100644 index 0000000..3542744 --- /dev/null +++ b/scanner/templates/report_template.html @@ -0,0 +1,92 @@ + + + + + WebSecScan Report - {{ scan.url }} + + + +

🛡️ WebSecScan Report

+

Target: {{ scan.url }}

+

Scan Time: {{ scan.datetime }}

+ +

🔐 SSL Certificate

+
    + {% for k, v in scan.ssl.items() %} +
  • {{ k }}: {{ v }}
  • + {% endfor %} +
+ +

🧱 WAF Detection

+

{{ scan.waf or 'None detected' }}

+ +

📑 Security Headers

+
    + {% for k, v in scan.headers.items() %} +
  • {{ k }}: {{ v }}
  • + {% endfor %} +
+ +

🧬 Technology Stack

+
    + {% if scan.tech_stack is defined and scan.tech_stack %} + {% for tech in scan.tech_stack %} +
  • {{ tech }}
  • + {% endfor %} + {% else %} +
  • None detected
  • + {% endif %} +
+ +

📌 CVE Matches

+
    + {% if scan.cve_matches is defined and scan.cve_matches %} + {% for k, v in scan.cve_matches.items() %} +
  • {{ k }}: {{ v | join(", ") }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %} +
+ +

📁 Sensitive Paths

+
    + {% if scan.dir_brute is defined and scan.dir_brute %} + {% for path in scan.dir_brute %} +
  • {{ path }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %} +
+ +

📊 Risk Score

+

{{ scan.risk.risk_score }}/100

+
    + {% for reason in scan.risk.penalties %} +
  • {{ reason }}
  • + {% endfor %} +
+ +

🔗 Links Analyzed

+ {% for r in scan.reports %} +
+

{{ r.link }}

+ {% if r.ip_grabber %}

⚠️ Possible IP grabber

{% endif %} +
    +
  • IP: {{ r.ip }}
  • +
  • WHOIS: {{ r.whois }}
  • +
  • Heuristics: {{ r.heuristics }}
  • +
+
+ {% endfor %} + + diff --git a/scanner/templates/report_template.md b/scanner/templates/report_template.md new file mode 100644 index 0000000..314ca41 --- /dev/null +++ b/scanner/templates/report_template.md @@ -0,0 +1,77 @@ +# 🛡️ WebSecScan Report + +**Target:** {{ scan.url }} +**Scan Time:** {{ scan.datetime }} + +--- + +## 🔐 SSL Certificate +{% for k, v in scan.ssl.items() %} +- **{{ k }}:** {{ v }} +{% endfor %} + +--- + +## 🧱 WAF Detection +**WAF:** {{ scan.waf or 'None detected' }} + +--- + +## 📑 Security Headers +{% for k, v in scan.headers.items() %} +- **{{ k }}:** {{ v }} +{% endfor %} + +--- + +## 🧬 Tech Stack +{% if scan.tech_stack is defined and scan.tech_stack %} +{% for tech in scan.tech_stack %} +- {{ tech }} +{% endfor %} +{% else %} +- None detected +{% endif %} + +--- + +## 📌 CVE Matches +{% if scan.cve_matches is defined and scan.cve_matches %} +{% for k, v in scan.cve_matches.items() %} +- **{{ k }}:** {{ v | join(", ") }} +{% endfor %} +{% else %} +- None +{% endif %} + +--- + +## 📁 Sensitive Paths Found +{% if scan.dir_brute is defined and scan.dir_brute %} +{% for path in scan.dir_brute %} +- `{{ path }}` +{% endfor %} +{% else %} +- None +{% endif %} + +--- + +## 📊 Risk Score +**Score:** {{ scan.risk.risk_score }}/100 +{% for reason in scan.risk.penalties %} +- ⚠️ {{ reason }} +{% endfor %} + +--- + +## 🔗 Links Analyzed +{% for r in scan.reports %} +### {{ r.link }} +{% if r.ip_grabber %} +> ⚠️ Possible IP grabber +{% endif %} +- **IP:** {{ r.ip }} +- **WHOIS:** {{ r.whois }} +- **Heuristics:** {{ r.heuristics }} +{% endfor %} diff --git a/setup.py b/setup.py index 2244600..78fb9ec 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,13 @@ 'websecscan = scanner.core:main' ] }, + package_data={ + "scanner": ["templates/*.html", "templates/*.md"], + }, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent" ], - python_requires='>=3.7', + python_requires='>=3.9', ) From 6f3d729063f87a0d6be5547eaf8145857af79d35 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 18:35:14 +0000 Subject: [PATCH 04/14] Update setup and metadata for Python 3.9 Co-authored-by: s9zqh6k6nr --- scanner/__pycache__/report.cpython-313.pyc | Bin 2660 -> 2889 bytes setup.py | 1 - websecscan.egg-info/PKG-INFO | 4 +--- websecscan.egg-info/SOURCES.txt | 3 +++ 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scanner/__pycache__/report.cpython-313.pyc b/scanner/__pycache__/report.cpython-313.pyc index f1bc81773cd6501a3cbfc74a365a0d1dad86d076..d348b9287a13bdb804a352cc3ac896dbb1aca6d1 100644 GIT binary patch delta 696 zcmY+C&ubGw6vt<}$t0UiHb0slQmclvNn9aqsT363La}S67cINx$&F*RROFY{P1bhVJ z`=_(DZ;X)NeBy-rT=YKdWYgO!QH2%vG6R+*6_z3u&m0{rD21IJ!*T9hmLkKPqrg}3 zsji$v2uKwdI{ERD95-i#Kt;fOKy}}wAgeJpMj)tqkRjEJ3_qocxU-e@Db#iqtLB<5 zzTf*i;?7{1!2YK2Y-CzQSq9$V%LwSr2AG#MbXt@x)BBZTpG47tlsbVdQue=fHZ_im@#rU?TiQO9OkAZ(6+pB3$)(u_LY!B$I zhVfjtS+ffIhG|R1{Oy(e-Ga7suVhD;3U~4kR?AwsaKE^kFAuUp%LF^<+=^y2sx=)T zfyTfuvfm-_bL^2GlHg5j@uKtfMs3s37hw^T{6A=Eh(7|Q^n{2fMEXT$ev%7+83~TQ zx$%1ANS^G-lZSHVNX~WSTwD5)NbNrCBxVjs^f;1wcl{uH>u_TJFtX4N9<%V3Vr{x!>gBuPrM$%Hh^MNnNri>6fwGUQ;1e|%$75!5Zn+<@D*30*?b zx}6dQbpeGJP~YdzqHpo=p7)&dzBA_~@)goYUay_LJ(Vwt z31$cdJ~2Tm3JF$}3~`r(Y)Fd?6hlU;hJw^8Jgs&=bn)7=neF@evrAgVi|+oLP<1H^ z%41NSLpkcBdYB`43G+@zj^}+OV+fm$E>8xMs0Tjlvekrksa`IDMGADZqt27uG8-k9_@d@OWJzaPiBA+B!mc2=R`|q<0MxQHvKj=PWx=LW0mYvvoY(-;r^QQpxxZM zVVsviV<1=08&xw80xp0e1`BPg-l~~xt5j>6WeXroTl5U$PX@}lXCow=RH=LQX8EFK o9Y6s+dT4ta@hx!9hfEtX&pXTg$Sj!b?Y{=L?wk!zOx(c}f4TKs9{>OV diff --git a/setup.py b/setup.py index 78fb9ec..11256e8 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ setup( name="websecscan", version="1.0.0", - author="Your Name", description="Advanced Website Security Scanner", packages=find_packages(), include_package_data=True, diff --git a/websecscan.egg-info/PKG-INFO b/websecscan.egg-info/PKG-INFO index 3e14e4c..c666dc7 100644 --- a/websecscan.egg-info/PKG-INFO +++ b/websecscan.egg-info/PKG-INFO @@ -2,11 +2,10 @@ Metadata-Version: 2.4 Name: websecscan Version: 1.0.0 Summary: Advanced Website Security Scanner -Author: Your Name Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent -Requires-Python: >=3.7 +Requires-Python: >=3.9 Requires-Dist: requests Requires-Dist: aiohttp Requires-Dist: beautifulsoup4 @@ -14,7 +13,6 @@ Requires-Dist: dnspython Requires-Dist: python-whois Requires-Dist: rich Requires-Dist: Jinja2 -Dynamic: author Dynamic: classifier Dynamic: requires-dist Dynamic: requires-python diff --git a/websecscan.egg-info/SOURCES.txt b/websecscan.egg-info/SOURCES.txt index 4aa408a..12dc3ca 100644 --- a/websecscan.egg-info/SOURCES.txt +++ b/websecscan.egg-info/SOURCES.txt @@ -1,3 +1,4 @@ +MANIFEST.in README.md setup.py scanner/__init__.py @@ -21,6 +22,8 @@ scanner/stack_fingerprint.py scanner/subdomains.py scanner/virustotal.py scanner/vuln_check.py +scanner/templates/report_template.html +scanner/templates/report_template.md websecscan.egg-info/PKG-INFO websecscan.egg-info/SOURCES.txt websecscan.egg-info/dependency_links.txt From 8a59ff39a0cec5df8d2b555bc39e5a306c0efd74 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 18:39:41 +0000 Subject: [PATCH 05/14] feat: Add GitHub Actions workflow for automated scans Co-authored-by: s9zqh6k6nr --- README.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/README.md b/README.md index fda9c7c..2e43f59 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,114 @@ python3 websecscan.py https://example.com --output md --active --- +## 🚀 Run on GitHub (scheduled, auto-published reports) + +Follow these steps to host the project on GitHub and keep it running on a schedule via GitHub Actions. Reports will be committed to a `gh-pages` branch and published with GitHub Pages. + +### 1) Push this project to your GitHub repo +- Create a new repository on GitHub (public or private) +- From your local machine or CI environment: +```bash +git init +git add . +git commit -m "feat: initial import of WebSecScan" +git branch -M main +git remote add origin git@github.com:/.git +git push -u origin main +``` + +### 2) Add your VirusTotal API key as a secret +- GitHub → Your repository → Settings → Secrets and variables → Actions → New repository secret +- Name: `VIRUSTOTAL_API_KEY` +- Value: your actual API key + +### 3) Add a `targets.txt` file (domains you own) +- Create a file named `targets.txt` in the repo root containing one authorized URL per line, for example: +```text +https://example.com +https://sub.example.com +``` + +### 4) Add the GitHub Actions workflow +- Create the file `.github/workflows/websecscan.yml` with the following content: +```yaml +name: WebSecScan + +on: + schedule: + - cron: "0 3 * * *" # daily at 03:00 UTC + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: websecscan + cancel-in-progress: true + +jobs: + scan: + runs-on: ubuntu-latest + env: + VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install WebSecScan + run: | + python -m pip install --upgrade pip + pip install . + + - name: Run scans + run: | + mkdir -p reports + TS=$(date -u +%Y%m%d_%H%M%S) + while IFS= read -r url; do + [ -z "$url" ] && continue + websecscan "$url" --output html --fast || true + dom=$(echo "$url" | sed 's|https\?://||; s|/||g') + if [ -f "reports/scan_${dom}.html" ]; then + mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" + fi + done < targets.txt + + - name: Upload artifact (reports) + uses: actions/upload-artifact@v4 + with: + name: reports-${{ github.run_number }} + path: reports/ + + - name: Publish to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: reports + keep_files: true +``` + +### 5) Enable GitHub Pages +- Repo → Settings → Pages → Build and deployment +- Source: `Deploy from a branch` +- Branch: `gh-pages`, Folder: `/ (root)` +- Save. Your reports will be available at `https://.github.io//` + +### 6) Run it now +- Go to Actions → `WebSecScan` → Run workflow +- After it finishes, view the Pages site or download the artifact + +### Notes and tips +- Only include domains you own or are authorized to scan in `targets.txt`. +- Remove `--fast` in the workflow to enable all checks (slower, more API use). +- To keep history, the workflow timestamps report filenames; adjust as needed. +- If you need private reports, skip GitHub Pages and rely on build artifacts only. +- For heavy scans or tighter control, consider a VPS + `cron`/`systemd` instead of Actions. + ## 🤝 Contributing Pull requests, feature suggestions, and plugins are welcome! Feel free to fork the project and submit your ideas, as I will be checking the project from time to time. From 38db3dd389887dfe8640f953761f16e0848a42fd Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:43:22 +0000 Subject: [PATCH 06/14] 2.0 --- .github/workflows/websecscan.yml | 58 ++++++++++++++++++++++++ MANIFEST.in | 1 + README.md | 2 +- reports/scan_example.com.md | 76 ++++++++++++++++++++++++++++++++ requirements.txt | 3 +- scanner/async_tools.py | 2 +- scanner/core.py | 10 +++-- scanner/extract.py | 4 +- scanner/headers.py | 4 +- scanner/profiling.py | 5 +-- scanner/report.py | 26 +++++++---- setup.py | 11 +++-- templates/report_template.html | 32 +++++++++----- templates/report_template.md | 14 +++++- 14 files changed, 212 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/websecscan.yml create mode 100644 MANIFEST.in create mode 100644 reports/scan_example.com.md diff --git a/.github/workflows/websecscan.yml b/.github/workflows/websecscan.yml new file mode 100644 index 0000000..29427c4 --- /dev/null +++ b/.github/workflows/websecscan.yml @@ -0,0 +1,58 @@ +name: WebSecScan + +on: + schedule: + - cron: "0 3 * * *" # daily at 03:00 UTC + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: websecscan + cancel-in-progress: true + +jobs: + scan: + runs-on: ubuntu-latest + env: + VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install WebSecScan + run: | + python -m pip install --upgrade pip + pip install . + + - name: Run scans + run: | + mkdir -p reports + TS=$(date -u +%Y%m%d_%H%M%S) + while IFS= read -r url; do + [ -z "$url" ] && continue + websecscan "$url" --output html --fast || true + dom=$(echo "$url" | sed 's|https\?://||; s|/||g') + if [ -f "reports/scan_${dom}.html" ]; then + mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" + fi + done < targets.txt + + - name: Upload artifact (reports) + uses: actions/upload-artifact@v4 + with: + name: reports-${{ github.run_number }} + path: reports/ + + - name: Publish to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: reports + keep_files: true \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..96a3bb6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include scanner/templates * \ No newline at end of file diff --git a/README.md b/README.md index fda9c7c..126bf60 100644 --- a/README.md +++ b/README.md @@ -118,4 +118,4 @@ Built for security researchers, red teamers, and educators. ## ⚠️ Disclaimer Use responsibly. This tool is intended for **authorized testing only**. -Do not scan websites without permission. +Do not scan websites without permission. \ No newline at end of file diff --git a/reports/scan_example.com.md b/reports/scan_example.com.md new file mode 100644 index 0000000..55245d9 --- /dev/null +++ b/reports/scan_example.com.md @@ -0,0 +1,76 @@ +# 🛡️ WebSecScan Report + +**Target:** https://example.com +**Scan Time:** 2025-10-27 18:18:22.048748 + +--- + +## 🔐 SSL Certificate + +- **issuer:** ((('countryName', 'US'),), (('organizationName', 'DigiCert Inc'),), (('commonName', 'DigiCert Global G3 TLS ECC SHA384 2020 CA1'),)) + +- **subject:** ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Los Angeles'),), (('organizationName', 'Internet Corporation for Assigned Names and Numbers'),), (('commonName', '*.example.com'),)) + +- **valid_from:** Jan 15 00:00:00 2025 GMT + +- **valid_until:** Jan 15 23:59:59 2026 GMT + + +--- + +## 🧱 WAF Detection +**WAF:** None detected + +--- + +## 📑 Security Headers + +- **Content-Security-Policy:** Missing + +- **X-Content-Type-Options:** Missing + +- **Strict-Transport-Security:** Missing + +- **X-Frame-Options:** Missing + + +--- + +## 🧬 Tech Stack + +- None detected + + +--- + +## 📌 CVE Matches + +- None + + +--- + +## 📁 Sensitive Paths Found + +- None + + +--- + +## 📊 Risk Score +**Score:** 25/100 + +- ⚠️ Missing headers: Content-Security-Policy, X-Content-Type-Options, Strict-Transport-Security, X-Frame-Options + +- ⚠️ No WAF detected + + +--- + +## 🔗 Links Analyzed + +### https://iana.org/domains/example + +- **IP:** 192.0.43.8 +- **WHOIS:** +- **Heuristics:** \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b0f63e5..cbbbf33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ python-whois>=0.8.0 Jinja2>=3.1.2 aiodns>=3.0.0 scikit-learn>1.3.0 -joblib>=1.3.2 \ No newline at end of file +joblib>=1.3.2 +scikit-learn \ No newline at end of file diff --git a/scanner/async_tools.py b/scanner/async_tools.py index dd191b3..1dac8ce 100644 --- a/scanner/async_tools.py +++ b/scanner/async_tools.py @@ -52,7 +52,7 @@ def load_plugins(plugin_folder="plugins"): spec.loader.exec_module(mod) if hasattr(mod, "run_plugin"): plugins.append(mod) - return plugins + return plugins def run_plugins(plugins, url, html, headers): results = [] diff --git a/scanner/core.py b/scanner/core.py index 1b5034e..10acb75 100644 --- a/scanner/core.py +++ b/scanner/core.py @@ -140,9 +140,9 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json", soup = BeautifulSoup(resp.text, 'html.parser') for script in soup.find_all('script'): if script.string: - js_snippets.appeend(script.string) + js_snippets.append(script.string) report["suspicious_analysis"] = is_url_suspicious(link, js_snippets) - except: + except Exception: report["suspicious_analysis"] = {"error": "Failed to analyze scripts"} @@ -157,10 +157,12 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json", report["ip"] = ip report["host_profile"] = get_host_profile(ip) - except: + except Exception: report["ip"] = None report["host_profile"] = {"error": "Could not resolve IP"} - scan_summary["reports"].append(report) + + # Always record the report for this link + scan_summary["reports"].append(report) # Final risk score scan_summary["risk"] = calculate_risk_score(scan_summary) diff --git a/scanner/extract.py b/scanner/extract.py index 68dfb28..41f67c2 100644 --- a/scanner/extract.py +++ b/scanner/extract.py @@ -16,5 +16,5 @@ def extract_links(url): def detect_ip_grabber(url): import re patterns = [r'\d{10,}', r'[a-zA-Z]{20,}', r'[0-9a-f]{32}'] - domains = ["grabify.link", "iplogger.org", "ipgrabber.io", "grabify.link"] - return any(re.search(p, url) for p in patterns) or any(d in url for d in domains) + domains = ["grabify.link", "iplogger.org", "ipgrabber.io"] + return any(re.search(p, url) for p in patterns) or any(d in url for d in domains) \ No newline at end of file diff --git a/scanner/headers.py b/scanner/headers.py index f01aab3..97e10b6 100644 --- a/scanner/headers.py +++ b/scanner/headers.py @@ -8,5 +8,5 @@ def analyze_headers(headers): return {h: headers.get(h, "Missing") for h in important} def detect_waf(headers): - waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuro-ID", "X-Akamai"] - return {k: headers[k] for k in waf_keys if k in headers} + waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuri-ID", "X-Akamai"] + return {k: headers[k] for k in waf_keys if k in headers} \ No newline at end of file diff --git a/scanner/profiling.py b/scanner/profiling.py index 0f3dc9f..f884378 100644 --- a/scanner/profiling.py +++ b/scanner/profiling.py @@ -99,7 +99,7 @@ def calculate_risk_score(scan_summary): if missing: penalties.append(f"Missing headers: {', '.join(missing)}") - if scan_summary.get("waf") in ["None", "", None]: + if not scan_summary.get("waf"): score += 5 penalties.append("No WAF detected") @@ -120,5 +120,4 @@ def calculate_risk_score(scan_summary): risk_score = min(score, 100) - return {"risk_score": risk_score, "penalties": penalties} - + return {"risk_score": risk_score, "penalties": penalties} \ No newline at end of file diff --git a/scanner/report.py b/scanner/report.py index b24d291..5fefbb3 100644 --- a/scanner/report.py +++ b/scanner/report.py @@ -2,26 +2,31 @@ import json from jinja2 import Environment, FileSystemLoader -template_loader = FileSystemLoader('./templates') -env = Environment(loader=template_loader) - -# Setup Jinja2 environment +# Setup Jinja2 environment once template_loader = FileSystemLoader("./templates") env = Environment(loader=template_loader) def export_html_report(scan_data, output_path): try: + # Ensure output directory exists + output_dir = os.path.dirname(output_path) or "." + os.makedirs(output_dir, exist_ok=True) + template = env.get_template("report_template.html") rendered = template.render(scan=scan_data) with open(output_path, "w", encoding="utf-8") as f: f.write(rendered) return True except Exception as e: - return f"HTML export failded: {e}" + return f"HTML export failed: {e}" def export_md_report(scan_data, output_path): try: + # Ensure output directory exists + output_dir = os.path.dirname(output_path) or "." + os.makedirs(output_dir, exist_ok=True) + template = env.get_template("report_template.md") rendered = template.render(scan=scan_data) with open(output_path, "w", encoding="utf-8") as f: @@ -31,7 +36,12 @@ def export_md_report(scan_data, output_path): return f"Markdown export failed: {e}" def save_scan_report(data, filename): - os.makedirs("reports", exist_ok=True) - with open(os.path.join("reports", filename), "w") as f: + """Save JSON report to the provided path. + + If 'filename' includes directories, they will be created automatically. + """ + output_dir = os.path.dirname(filename) or "." + os.makedirs(output_dir, exist_ok=True) + with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) - print(f"✅ Saved to reports/{filename}") \ No newline at end of file + print(f"✅ Saved to {filename}") \ No newline at end of file diff --git a/setup.py b/setup.py index b582985..0c01547 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,8 @@ setup( name="websecscan", version="1.0.0", - author="Your Name", + author="Arli", + author_email='arliturka2@gmail.com', description="Advanced Website Security Scanner", packages=find_packages(), include_package_data=True, @@ -18,13 +19,17 @@ ], entry_points={ 'console_scripts': [ - 'websecscan = websecscan:main' + 'websecscan = scanner:main' ] }, + package_data={ + "scanner": ["templates/*.html", "templates/*.md"], + + }, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent" ], python_requires='>=3.7', -) +) \ No newline at end of file diff --git a/templates/report_template.html b/templates/report_template.html index 9f6b5eb..5e7eac9 100644 --- a/templates/report_template.html +++ b/templates/report_template.html @@ -37,23 +37,35 @@

📑 Security Headers

🧬 Technology Stack

    - {% for tech in scan.tech_stack %} -
  • {{ tech }}
  • - {% endfor %} + {% if scan.tech_stack is defined and scan.tech_stack %} + {% for tech in scan.tech_stack %} +
  • {{ tech }}
  • + {% endfor %} + {% else %} +
  • None detected
  • + {% endif %}

📌 CVE Matches

    - {% for k, v in scan.cve_matches.items() %} -
  • {{ k }}: {{ v | join(", ") }}
  • - {% endfor %} + {% if scan.cve_matches is defined and scan.cve_matches %} + {% for k, v in scan.cve_matches.items() %} +
  • {{ k }}: {{ v | join(", ") }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %}

📁 Sensitive Paths

    - {% for path in scan.dir_brute %} -
  • {{ path }}
  • - {% endfor %} + {% if scan.dir_brute is defined and scan.dir_brute %} + {% for path in scan.dir_brute %} +
  • {{ path }}
  • + {% endfor %} + {% else %} +
  • None
  • + {% endif %}

📊 Risk Score

@@ -77,4 +89,4 @@

🔗 Links Analyzed

{% endfor %} - + \ No newline at end of file diff --git a/templates/report_template.md b/templates/report_template.md index a3ef0fd..78c59dd 100644 --- a/templates/report_template.md +++ b/templates/report_template.md @@ -25,23 +25,35 @@ --- ## 🧬 Tech Stack +{% if scan.tech_stack is defined and scan.tech_stack %} {% for tech in scan.tech_stack %} - {{ tech }} {% endfor %} +{% else %} +- None detected +{% endif %} --- ## 📌 CVE Matches +{% if scan.cve_matches is defined and scan.cve_matches %} {% for k, v in scan.cve_matches.items() %} - **{{ k }}:** {{ v | join(", ") }} {% endfor %} +{% else %} +- None +{% endif %} --- ## 📁 Sensitive Paths Found +{% if scan.dir_brute is defined and scan.dir_brute %} {% for path in scan.dir_brute %} - `{{ path }}` {% endfor %} +{% else %} +- None +{% endif %} --- @@ -62,4 +74,4 @@ - **IP:** {{ r.ip }} - **WHOIS:** {{ r.whois }} - **Heuristics:** {{ r.heuristics }} -{% endfor %} +{% endfor %} \ No newline at end of file From a4dbb24c261b4fc5df4b7b34f30c2c47a618e90c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 27 Oct 2025 19:05:24 +0000 Subject: [PATCH 07/14] Add GitHub Actions workflow for automated websecscan Co-authored-by: s9zqh6k6nr --- .github/workflows/websecscan.yml | 64 ++++++++++++++++++++++ templates/report_template.html | 92 -------------------------------- templates/report_template.md | 77 -------------------------- 3 files changed, 64 insertions(+), 169 deletions(-) create mode 100644 .github/workflows/websecscan.yml delete mode 100644 templates/report_template.html delete mode 100644 templates/report_template.md diff --git a/.github/workflows/websecscan.yml b/.github/workflows/websecscan.yml new file mode 100644 index 0000000..f9f673a --- /dev/null +++ b/.github/workflows/websecscan.yml @@ -0,0 +1,64 @@ +name: WebSecScan + +on: + schedule: + - cron: "0 3 * * *" # daily at 03:00 UTC + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: websecscan + cancel-in-progress: true + +jobs: + scan: + runs-on: ubuntu-latest + env: + VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install WebSecScan + run: | + python -m pip install --upgrade pip + pip install . + + - name: Run scans + run: | + mkdir -p reports + TS=$(date -u +%Y%m%d_%H%M%S) + if [ -f targets.txt ]; then + while IFS= read -r url; do + [ -z "$url" ] && continue + websecscan "$url" --output html --fast || true + dom=$(echo "$url" | sed 's|https\?://||; s|/||g') + if [ -f "reports/scan_${dom}.html" ]; then + mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" + fi + done < targets.txt + else + echo "No targets.txt found; skipping scans." + fi + # Disable Jekyll on GitHub Pages + touch reports/.nojekyll + + - name: Upload artifact (reports) + uses: actions/upload-artifact@v4 + with: + name: reports-${{ github.run_number }} + path: reports/ + + - name: Publish to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: reports + keep_files: true diff --git a/templates/report_template.html b/templates/report_template.html deleted file mode 100644 index 3542744..0000000 --- a/templates/report_template.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - WebSecScan Report - {{ scan.url }} - - - -

🛡️ WebSecScan Report

-

Target: {{ scan.url }}

-

Scan Time: {{ scan.datetime }}

- -

🔐 SSL Certificate

-
    - {% for k, v in scan.ssl.items() %} -
  • {{ k }}: {{ v }}
  • - {% endfor %} -
- -

🧱 WAF Detection

-

{{ scan.waf or 'None detected' }}

- -

📑 Security Headers

-
    - {% for k, v in scan.headers.items() %} -
  • {{ k }}: {{ v }}
  • - {% endfor %} -
- -

🧬 Technology Stack

-
    - {% if scan.tech_stack is defined and scan.tech_stack %} - {% for tech in scan.tech_stack %} -
  • {{ tech }}
  • - {% endfor %} - {% else %} -
  • None detected
  • - {% endif %} -
- -

📌 CVE Matches

-
    - {% if scan.cve_matches is defined and scan.cve_matches %} - {% for k, v in scan.cve_matches.items() %} -
  • {{ k }}: {{ v | join(", ") }}
  • - {% endfor %} - {% else %} -
  • None
  • - {% endif %} -
- -

📁 Sensitive Paths

-
    - {% if scan.dir_brute is defined and scan.dir_brute %} - {% for path in scan.dir_brute %} -
  • {{ path }}
  • - {% endfor %} - {% else %} -
  • None
  • - {% endif %} -
- -

📊 Risk Score

-

{{ scan.risk.risk_score }}/100

-
    - {% for reason in scan.risk.penalties %} -
  • {{ reason }}
  • - {% endfor %} -
- -

🔗 Links Analyzed

- {% for r in scan.reports %} -
-

{{ r.link }}

- {% if r.ip_grabber %}

⚠️ Possible IP grabber

{% endif %} -
    -
  • IP: {{ r.ip }}
  • -
  • WHOIS: {{ r.whois }}
  • -
  • Heuristics: {{ r.heuristics }}
  • -
-
- {% endfor %} - - diff --git a/templates/report_template.md b/templates/report_template.md deleted file mode 100644 index 314ca41..0000000 --- a/templates/report_template.md +++ /dev/null @@ -1,77 +0,0 @@ -# 🛡️ WebSecScan Report - -**Target:** {{ scan.url }} -**Scan Time:** {{ scan.datetime }} - ---- - -## 🔐 SSL Certificate -{% for k, v in scan.ssl.items() %} -- **{{ k }}:** {{ v }} -{% endfor %} - ---- - -## 🧱 WAF Detection -**WAF:** {{ scan.waf or 'None detected' }} - ---- - -## 📑 Security Headers -{% for k, v in scan.headers.items() %} -- **{{ k }}:** {{ v }} -{% endfor %} - ---- - -## 🧬 Tech Stack -{% if scan.tech_stack is defined and scan.tech_stack %} -{% for tech in scan.tech_stack %} -- {{ tech }} -{% endfor %} -{% else %} -- None detected -{% endif %} - ---- - -## 📌 CVE Matches -{% if scan.cve_matches is defined and scan.cve_matches %} -{% for k, v in scan.cve_matches.items() %} -- **{{ k }}:** {{ v | join(", ") }} -{% endfor %} -{% else %} -- None -{% endif %} - ---- - -## 📁 Sensitive Paths Found -{% if scan.dir_brute is defined and scan.dir_brute %} -{% for path in scan.dir_brute %} -- `{{ path }}` -{% endfor %} -{% else %} -- None -{% endif %} - ---- - -## 📊 Risk Score -**Score:** {{ scan.risk.risk_score }}/100 -{% for reason in scan.risk.penalties %} -- ⚠️ {{ reason }} -{% endfor %} - ---- - -## 🔗 Links Analyzed -{% for r in scan.reports %} -### {{ r.link }} -{% if r.ip_grabber %} -> ⚠️ Possible IP grabber -{% endif %} -- **IP:** {{ r.ip }} -- **WHOIS:** {{ r.whois }} -- **Heuristics:** {{ r.heuristics }} -{% endfor %} From 4bc2a62b6698891a1c901e1979906790a32762c8 Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:05:28 +0000 Subject: [PATCH 08/14] Fixed some errors --- .github/workflows/websecscan.yml | 1 + targets.txt | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 targets.txt diff --git a/.github/workflows/websecscan.yml b/.github/workflows/websecscan.yml index 29427c4..8f6d5c5 100644 --- a/.github/workflows/websecscan.yml +++ b/.github/workflows/websecscan.yml @@ -42,6 +42,7 @@ jobs: mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" fi done < targets.txt + touch reports/.nojekyll - name: Upload artifact (reports) uses: actions/upload-artifact@v4 diff --git a/targets.txt b/targets.txt new file mode 100644 index 0000000..a8ccda9 --- /dev/null +++ b/targets.txt @@ -0,0 +1,2 @@ +https://youtube.com +https://facepunch.com From c5d2bc6b5e7a4acc1db34641c86638a77e2a2634 Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:20:38 +0000 Subject: [PATCH 09/14] ada --- templates/{report_template.md => report_template.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/{report_template.md => report_template.txt} (100%) diff --git a/templates/report_template.md b/templates/report_template.txt similarity index 100% rename from templates/report_template.md rename to templates/report_template.txt From 127f149aaa194a173b341dabd78f0c844f0da555 Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:38:14 +0100 Subject: [PATCH 10/14] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0c01547..208b61c 100644 --- a/setup.py +++ b/setup.py @@ -31,5 +31,5 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent" ], - python_requires='>=3.7', + python_requires='>=3.9', ) \ No newline at end of file From e41701f545eedda88437fa592078d3532f1fd366 Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:46:27 +0100 Subject: [PATCH 11/14] Update report.py --- scanner/report.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scanner/report.py b/scanner/report.py index 5fefbb3..47bad7f 100644 --- a/scanner/report.py +++ b/scanner/report.py @@ -1,9 +1,12 @@ import os import json from jinja2 import Environment, FileSystemLoader +from pathlib import Path -# Setup Jinja2 environment once -template_loader = FileSystemLoader("./templates") +# Setup Jinja2 environment once, resolving templates +PACKAGE_DIR = Path(__file__).resolve().parent +DEFAULT_TEMPLATE_DIR = PACKAGE_DIR / "templates" +template_loader = FileSystemLoader(str(DEFAULT_TEMPLATE_DIR)) env = Environment(loader=template_loader) @@ -36,10 +39,6 @@ def export_md_report(scan_data, output_path): return f"Markdown export failed: {e}" def save_scan_report(data, filename): - """Save JSON report to the provided path. - - If 'filename' includes directories, they will be created automatically. - """ output_dir = os.path.dirname(filename) or "." os.makedirs(output_dir, exist_ok=True) with open(filename, "w", encoding="utf-8") as f: From 0975092cb9ce7899f309a3a00caf27f2269546b7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 28 Oct 2025 04:07:17 +0000 Subject: [PATCH 12/14] Add index.html to websecscan reports artifact Co-authored-by: s9zqh6k6nr --- .github/workflows/websecscan.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/websecscan.yml b/.github/workflows/websecscan.yml index f9f673a..713ebcf 100644 --- a/.github/workflows/websecscan.yml +++ b/.github/workflows/websecscan.yml @@ -49,6 +49,13 @@ jobs: # Disable Jekyll on GitHub Pages touch reports/.nojekyll + # Generate a simple index.html listing all report files + echo "WebSecScan Reports

WebSecScan Reports

Generated by GitHub Actions
    " > reports/index.html + for f in $(ls -1t reports/*.html 2>/dev/null | sed 's|reports/||' | grep -v '^index.html$'); do + echo "
  • $f
  • " >> reports/index.html + done + echo "

To run a new scan, go to Actions and trigger the WebSecScan workflow.

" >> reports/index.html + - name: Upload artifact (reports) uses: actions/upload-artifact@v4 with: From f49c9f7cc7d4d93ae19449d449a9b7e7e1e903c0 Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Tue, 28 Oct 2025 05:15:45 +0100 Subject: [PATCH 13/14] Update websecscan.yml --- .github/workflows/websecscan.yml | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/websecscan.yml b/.github/workflows/websecscan.yml index 8f6d5c5..4b7237e 100644 --- a/.github/workflows/websecscan.yml +++ b/.github/workflows/websecscan.yml @@ -2,7 +2,7 @@ name: WebSecScan on: schedule: - - cron: "0 3 * * *" # daily at 03:00 UTC + - cron: "0 3 * * *" # daily at 03:00 UTC workflow_dispatch: permissions: @@ -34,16 +34,28 @@ jobs: run: | mkdir -p reports TS=$(date -u +%Y%m%d_%H%M%S) - while IFS= read -r url; do - [ -z "$url" ] && continue - websecscan "$url" --output html --fast || true - dom=$(echo "$url" | sed 's|https\?://||; s|/||g') - if [ -f "reports/scan_${dom}.html" ]; then - mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" - fi - done < targets.txt + if [ -f targets.txt ]; then + while IFS= read -r url; do + [ -z "$url" ] && continue + websecscan "$url" --output html --fast || true + dom=$(echo "$url" | sed 's|https\?://||; s|/||g') + if [ -f "reports/scan_${dom}.html" ]; then + mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html" + fi + done < targets.txt + else + echo "No targets.txt found; skipping scans." + fi + # Disable Jekyll on GitHub Pages touch reports/.nojekyll + # Generate a simple index.html listing all report files + echo "WebSecScan Reports

WebSecScan Reports

Generated by GitHub Actions
    " > reports/index.html + for f in $(ls -1t reports/*.html 2>/dev/null | sed 's|reports/||' | grep -v '^index.html$'); do + echo "
  • $f
  • " >> reports/index.html + done + echo "

To run a new scan, go to Actions and trigger the WebSecScan workflow.

" >> reports/index.html + - name: Upload artifact (reports) uses: actions/upload-artifact@v4 with: From 0fbf7e53a0ecb3f532c0c5c41ec5c6c504e394fd Mon Sep 17 00:00:00 2001 From: Arli <35976946+ArliT1-F@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:16:52 +0200 Subject: [PATCH 14/14] Add Riot Games URL to targets list --- targets.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/targets.txt b/targets.txt index a8ccda9..46cafae 100644 --- a/targets.txt +++ b/targets.txt @@ -1,2 +1,3 @@ https://youtube.com https://facepunch.com +https://riotgames.com