From 2472b76ac25f5dc4974d9c122406444506aa4f99 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 05:51:59 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL/H?= =?UTF-8?q?IGH]=20Fix=20hardcoded=20secrets=20and=20improve=20CORS=20polic?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move hardcoded HMAC SECRET_KEY to an environment variable in backend/main.py and backend/DivineoBunker.py. - Implement configurable CORS origins using LVT_ALLOWED_ORIGINS environment variable, defaulting to * for development but providing a clear path for production hardening. - Fix backend/tests/test_main.py by updating the UserScan schema and implementing HMAC signature generation for authentication. - Update backend/main.py exception handling to return a 503 Service Unavailable status code for AI engine failures as required by security specifications. - Ensure python-dotenv is initialized in the backend to load all required environment variables. - Verified that all backend tests pass with these security improvements. Co-authored-by: LVT-ENG <214667862+LVT-ENG@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ backend/DivineoBunker.py | 3 ++- .../__pycache__/jules_engine.cpython-312.pyc | Bin 0 -> 1996 bytes backend/__pycache__/main.cpython-312.pyc | Bin 0 -> 4600 bytes backend/__pycache__/models.cpython-312.pyc | Bin 0 -> 1369 bytes backend/main.py | 20 +++++++++++++++--- .../test_main.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 3577 bytes backend/tests/test_main.py | 16 +++++++++++--- 8 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 .jules/sentinel.md create mode 100644 backend/__pycache__/jules_engine.cpython-312.pyc create mode 100644 backend/__pycache__/main.cpython-312.pyc create mode 100644 backend/__pycache__/models.cpython-312.pyc create mode 100644 backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..2e5cd63 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-05-15 - [Securing HMAC Secrets and CORS] +**Vulnerability:** Hardcoded HMAC secret key and overly permissive CORS policy. +**Learning:** Hardcoding production-level secrets in multiple files (`main.py` and `DivineoBunker.py`) creates a high risk of exposure. Permissive CORS (`"*"`) is acceptable for rapid prototyping but should be configurable for deployment to prevent unauthorized cross-origin access. +**Prevention:** Use environment variables (via `os.getenv` and `python-dotenv`) for all sensitive credentials. Implement configurable CORS origins through environment variables, defaulting to specific origins in production. diff --git a/backend/DivineoBunker.py b/backend/DivineoBunker.py index ca01742..81cea01 100644 --- a/backend/DivineoBunker.py +++ b/backend/DivineoBunker.py @@ -2,11 +2,12 @@ import hashlib import time import json +import os class DivineoBunker: def __init__(self): # 🛡️ Configuración Maestra (abvetos.com) - self.secret_key = "LVT_SECRET_PROD_091228222" + self.secret_key = os.getenv("LVT_SECRET_KEY", "LVT_DEV_SECRET_DO_NOT_USE_IN_PROD") self.patent = "PCT/EP2025/067317" self.algorithm_v = "V10_Divineo_Shopify_Final" diff --git a/backend/__pycache__/jules_engine.cpython-312.pyc b/backend/__pycache__/jules_engine.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78615be36a9ecb40cc61907787408a9cb5f7139d GIT binary patch literal 1996 zcma)6O>7fK6rQzry|&}T{DnYN!W06*tz!ZWRZB}HpiW2&2oV}eF4lN=Y>%_vUCoXi zY&n%Hp*A_ust1s&O1bn<(L;|tRXz36Lmh>RuoY5JPrbPfr=I$D*CvEh=dkNqUUr=P_yO^OlPmmT%F0Czc!QmFngZ6n3)(N8Mn*|H3hB_&Pd*%P6l>TiOq5gwBFcN zq!nhe@l%;I;{}5_B`vfK7sbH#Aa>A+snIZ_LFpjA?38P6Tx@0ir6FV3tV??1;Jnykfy@yvRh!stWA!2dmz6-qP#X ztXA@rJJ{mbVGUr^FX+lT(?rAcom*BF6Hf8i2UVbaER6{+v0`Z)1jGfP8OmYJGM%a+ z2#7-jo+bw64B%WQ1yZL%(4g#jtQpJ`kU1c!pqx5_`s#`j)OTf*RA?hQ2C8$|rIE?R zeFQhCRx+6eN4jW1+HG;xpB~3kB+r?K-{-`pXR)j3p3xZIxtfJu5KLpD4k2;++#}yY z4|F#m&O%T#d)qb$wA*5-jztM@776!z4onZEO?iQ80&69UY19V_)68Sf0D`@<;yiJv z?qhHSz!OFt14Sik(XUVlFjuUaua}OAPHX|h`x&!0uFqV&fvn3L zo;;lj1bZWcr!1`s?E!sd&>}H4ng+2eAP^uR&;cGP1!5T3D!g#KAbm1}uW_r!bm}jG z+&1(H^{(?2r>0g)1A&HNEkYCI>o{u`4d#^mva>cZ!4B%ABCc8@4iSQL6>b5jSc41numwm^s^1M+j=Q&LSw(|px@@r;B@EaIxC=Z5JeZ6G zk2m+8LpK8Fgcq25yts? z_#Qt3uJ-~v0zRFvBuRgu*b{X02^#r3f~3Tq{>}K_4);AO_XdYpC>;}-dBG|cq4jfS$Pya Wv=%+o9+_B=o@yyu@~hH8AJu<4a33)M literal 0 HcmV?d00001 diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e289fa8b4604af61625e17790cc3f809052d0b23 GIT binary patch literal 4600 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 JNN#i0_HWf|WM(R61_w(PFm-8Gd_ zFDC7FyN z7`eHhji`dqpTZc8=#ZTK0OTd2h)O21q?RNJ9x_9g+>$M&rC4f9l~4$+BPzc@RAJJQ ze6h9_4!kOO!|!<`G(t5Rr7;?(37Vv-R}q?~8JeXznx_Ri7I=!ULM<(XSd?j>jg1cV zZax~)BWbu-ynm%q8VI8z@#qS1*+z#lvm$e4)zCez)D73;+Gf`?IyN!&;rd`2o)?`h zg833L)RHJ_g`OfNGlfd`!5gqpz*O+bmp(fwA<*L`&V>Va6 zAY$_v+h?|iy?&Q9L_Wqgu^7fYhOyP5JyY-m#-I0yIgH4z=kPSf#I`#g5l43+M{S!O z;{Axu#OePoR!O&8{gmji6|KUDnCaf?_8kp^0l?EDw+QeJ@{^gvx*ty+KKA2+W%4gJ zox&*b-$mJm@c$4+$Pvi-!u67+mXFRq~9D{(S25nR#PEuSXB_J&zBOogv2f$^+_ME&h zgA0qIa;}U(G=e(~02*oW!*xHFJgoWo(y>zV3**Pi_)s*V+&j_4a-LmTu5T>Y8o1fK zy@;Fj+Qva~g_ssG>~hmItl5W#SANWh?GBb9f43csza9({VD&_w&ELAcG*|xP>z@v$ zb`APrwdy|abd7eu`ow{<(7m0DTUPJfSy)(Hgo~D7kuxq}Lcmo4*93eZU=o05>uWo; zCSI7I!_Dojwe3$H9c1ck-*C&|DmV8G+wKomA#?Zp@5DHLFqj3a7Xg@ujQ>8(-Mjbh zEiF|-P8pUsQvi)hf@|GAg%>e&M|2erL$w?Bt|MyDQ3Zqr1hsjgUh~oBliI^)xYpQN zYiw_BJ>!Zgx+T0%%pP-jY<+oa10r~JIT z^cysNt|(IB+x|I%={ykuZk!{SPD&_!-Ot?c$FKSmlYTbuXRrCik51FtMEIl-#IC;= nn;6AT$MR#L*C=~BgHpNIxv5`sGjDP;$GMxw$=R=BL2bPS4e(Yu literal 0 HcmV?d00001 diff --git a/backend/main.py b/backend/main.py index cb988e1..14aa881 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,24 +2,31 @@ import hashlib import time import json +import os +from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from models import UserScan, SHOPIFY_INVENTORY from jules_engine import get_jules_advice +load_dotenv() + app = FastAPI(title="Divineo Bunker Backend") +# 🔒 Security: In production, configure LVT_ALLOWED_ORIGINS in .env +ALLOWED_ORIGINS = os.getenv("LVT_ALLOWED_ORIGINS", "*").split(",") + app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 🛡️ Configuración Maestra (abvetos.com) -SECRET_KEY = "LVT_SECRET_PROD_091228222" +SECRET_KEY = os.getenv("LVT_SECRET_KEY", "LVT_DEV_SECRET_DO_NOT_USE_IN_PROD") PATENT = "PCT/EP2025/067317" def verify_auth(user_id: str, token: str) -> bool: @@ -68,7 +75,14 @@ async def recommend_garment(scan: UserScan, garment_id: str = "BALMAIN_SS26_SLIM # Usamos Jules para el toque de estilo styling_advice = get_jules_advice(scan, item) except Exception as e: - styling_advice = f"Divineo confirmado con {item['name']}." + return JSONResponse( + status_code=503, + content={ + "status": "error", + "code": 503, + "message": "Jules AI Engine is currently recalibrating or unavailable. Please try again." + } + ) if is_divineo and item['stock'] > 0: return { diff --git a/backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc b/backend/tests/__pycache__/test_main.cpython-312-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8de80a6a93132355a997aef145b2bc07acdc8b4 GIT binary patch literal 3577 zcmb_e|7#n`6`z$>D{1w`vSlZB)10?7akZ2xXFIm>Wa-=%BA$D`>D>kG;lxl&8}8b zf@$uDF4DX=Gw;25^X4<}&7U(F8NpYX{=|41L+GEp6MoSybL0VY59vr3OjHtl8mULi z5dqc3QcMVDq>|*%#7l9XCQ1pPCQC`65mTqOuZg0*FcZgird+^o7kn7W(E*p9o(zf8nL#bLKG7~N^p{U9%>bR6e4a;SsYt#u7t94vvQH$K5JT^=sR@v4Gi&wEzHH}p!IaNF} zH_wt)a#J@da4c$+@=&vg>wr0<;&7J~Mfqy-9Gug+Ra{;tmVV9!QO9q!IyS6gqs8(S zViAg6qVY-1wLy@c0BsIVrwq*|dX!B6YUTaWdlSE&_|2(K=5#0d(&n3wj=DXd)p{Tbts$hxz3A@~p~bf7iN^t~9n+I-$%}c?dI6z! z+>1LO|8LOgvKLP}uq&e(wckx-{kJu%!V9h4mHON3ls z(h^|({#7|nHWpfaft(b|>6!b{4M1Z%p=b3WFJTCNFX^5P*B`Jn9**^?mkce1^j$&E z-4xm}_{SmUV(nC4I^=p6P1c9?q&}kO??>AyPxey&`NX`K^TEKn$0P+fpBS9aDAyS{ zpRxbj`N-jZUU)t|NjRSuonZrKzcFGQBt<+bx1G@{2Ml8GNo)&7M76p?q5TnT4-| zWxQ$<<%Z!_L8L-)nXENUSR68Ivka<4SKeBAds!ir+O+5!D2K@56p{>@g#Qu8<6xcA zg`IB7c~?*)%NwUwjC#|Abg6Tvmjb7M5Nr)56(;TcL% z=ok<-R=3OR+Vfl8xa_CkUNp4|rVv$E3u2>nBUpURw9D9ZUN0VNh39UE*DJ`|*^|5D zbDiw`ZepJD+qZGLTM3GO`hVU5mhm0%3;FcSYy~vnyLyp{SFnjyF{m*yzecdw4Qx0r zlgV}NqT6bajqI~%fOv>C$F_{85JFY>Dzdn!3LCR$3ys!{>d3dcjIyY3?q$^}Ixcpb zj>fB<+*THYi8<9_2>?#Rwj82HSrlY>GsBy?jVyNH;o3<`3cI1a85 z*wsjP3!&4q;EAYX7poCE1wrI)%|7ERY>PtWKx&r79ReG7sb6=QRQBIkOl;T?-_$@% zn0&F(@{b5pHmrg3rbbDF!tHBdN?01EwbiC!!hQgF0ZiDq=2Y#bsr#Z@#k5zksaY5v z8cEH;YlK^1**E+s0&ti36=8X8&8Bq>*t^*}#bE&6TlZic1mwzW=L4eXYy^fZhG zxQI7!%e1jh&+`4IxG%xcOb>R>4|ZmQPO#McVCNNDe9p?u;PiQizt(s<>V3h@I{US* z5M2UoUUr>zfaWIpGAqOu4ihMr+ZRvniEwKcc3)loRDAnTLPIZYUU{7V#-rgg4}RVm z{@%gx*ujZ!K1s%gq|Gb;&Y|3iJGXA%+CBB%hu_)0)fu|5`9si2kM5@?_fnI);?#p# zXm(SR4`%mLQ~M(S{jEC?^m^@{IJNys(ChTsPkrHF_^;etPrk>1^y5_i&f@LG_rAY* z`SHlaK`wug$4}zosC0g#01RNQNa6KsxPZ~#5Q}%m z=y|Rq1uB#Sf$~>5x+5`yjj7p!qlU;qFB literal 0 HcmV?d00001 diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index 8d756a9..6f60c36 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -1,9 +1,17 @@ import pytest +import hmac +import hashlib +import time from fastapi.testclient import TestClient -from backend.main import app +from backend.main import app, SECRET_KEY client = TestClient(app) +def generate_test_token(user_id: str): + ts = str(int(time.time())) + sig = hmac.new(SECRET_KEY.encode(), f"{user_id}:{ts}".encode(), hashlib.sha256).hexdigest() + return f"{ts}.{sig}" + def test_recommend_garment_engine_failure(monkeypatch): """ Test that the /api/recommend endpoint correctly handles failures @@ -18,9 +26,11 @@ def mock_get_jules_advice(*args, **kwargs): monkeypatch.setattr("backend.main.get_jules_advice", mock_get_jules_advice) # 2. Prepare the request payload + user_id = "test_user_123" payload = { - "height": 175.0, - "weight": 68.0, + "user_id": user_id, + "token": generate_test_token(user_id), + "waist": 72.0, "event_type": "Gala" }