cazyundee commited on
Commit
13dcd95
Β·
verified Β·
1 Parent(s): f1ffefe

security: require a server-held shared secret for the compute routes

Browse files
Files changed (1) hide show
  1. app.py +40 -0
app.py CHANGED
@@ -882,6 +882,46 @@ def _patched_create_app(blocks, **kwargs):
882
  except Exception as _se:
883
  _log(f"searxng bridge registration failed: {_se}")
884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  # ── CORS gate for /respite/* ────────────────────────────────────────────
886
  # Gradio installs a global CORS middleware that reflects the caller's
887
  # Origin and sets `Access-Control-Allow-Credentials: true`. Applied to the
 
882
  except Exception as _se:
883
  _log(f"searxng bridge registration failed: {_se}")
884
 
885
+ # ── Shared-secret gate for the unauthenticated compute routes ────────────
886
+ # /respite/audio/generate, /respite/search, /respite/text/* and
887
+ # /respite/benchmark* had no authentication of any kind: any process on the
888
+ # internet could generate audio, run inference, and bill the Space owner's
889
+ # audio key and ZeroGPU hours. CORS is irrelevant to them β€” a server-side
890
+ # caller does not care what the browser enforces β€” so the boundary they
891
+ # were missing is a shared secret, not a header policy.
892
+ #
893
+ # The secret is read from the Space's own `RESPIRE_SPACE_KEY` setting and is
894
+ # never sent to a browser: the app holds it server-side and proxies through
895
+ # `/api/audio`. If the setting is absent the routes fail CLOSED, because a
896
+ # gate that opens when its key is missing is a gate that opens exactly when
897
+ # it is not configured.
898
+ try:
899
+ import hmac as _hmac
900
+
901
+ _SPACE_KEY = os.environ.get("RESpite_SPACE_KEY", "")
902
+ _PROTECTED_PREFIXES = (
903
+ "/respite/audio/",
904
+ "/respite/search",
905
+ "/respite/text/",
906
+ "/respite/benchmark",
907
+ )
908
+
909
+ @fa_app.middleware("http")
910
+ async def _respite_key_gate(request, call_next):
911
+ if request.url.path.startswith(_PROTECTED_PREFIXES):
912
+ if not _SPACE_KEY:
913
+ return JSONResponse(
914
+ {"error": "service not configured"}, status_code=503
915
+ )
916
+ presented = request.headers.get("x-respite-key", "")
917
+ if not _hmac.compare_digest(presented, _SPACE_KEY):
918
+ return JSONResponse({"error": "unauthorized"}, status_code=401)
919
+ return await call_next(request)
920
+
921
+ _log(f"compute routes key gate installed (key {'set' if _SPACE_KEY else 'MISSING'})")
922
+ except Exception as _ke:
923
+ _log(f"compute routes key gate FAILED to install: {_ke}")
924
+
925
  # ── CORS gate for /respite/* ────────────────────────────────────────────
926
  # Gradio installs a global CORS middleware that reflects the caller's
927
  # Origin and sets `Access-Control-Allow-Credentials: true`. Applied to the