MichaelMintIcecream commited on
Commit
f78bc70
·
verified ·
1 Parent(s): 04f1403

security: constant-time token compare + num_steps/image caps

Browse files
Files changed (1) hide show
  1. molmoact2_server.py +12 -4
molmoact2_server.py CHANGED
@@ -22,6 +22,7 @@ Deploy + test: see README.md in this directory.
22
  import base64
23
  import io
24
  import os
 
25
  import threading
26
  from typing import Optional
27
 
@@ -37,6 +38,10 @@ NORM_TAG = os.environ.get("MOLMOACT_NORM_TAG", "so100_so101_molmoact2")
37
  AUTH_TOKEN = os.environ.get("NORI_INFER_TOKEN") # REQUIRED — the rollout sends it
38
  # bf16 fits <16GB (A10G/L4). Set MOLMOACT_BF16=0 to run fp32 (~26GB, needs L40S/48GB).
39
  DTYPE = torch.bfloat16 if os.environ.get("MOLMOACT_BF16", "1") == "1" else torch.float32
 
 
 
 
40
 
41
  app = FastAPI(title="nori-molmoact2")
42
  _model = None
@@ -116,13 +121,16 @@ def health() -> dict:
116
 
117
  @app.post("/act", response_model=ActResponse)
118
  def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
119
- if authorization != f"Bearer {AUTH_TOKEN}":
 
 
120
  raise HTTPException(status_code=401, detail="bad or missing bearer token")
121
  if _model is None:
122
  detail = f"model load failed: {_load_error}" if _load_error else "model not loaded yet"
123
  raise HTTPException(status_code=503, detail=detail)
124
- if len(req.images) < 1:
125
- raise HTTPException(status_code=422, detail="need at least one camera image")
 
126
  images = [_decode(b) for b in req.images]
127
  state = np.asarray(req.state, dtype=np.float32)
128
  with _lock, torch.no_grad():
@@ -133,7 +141,7 @@ def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResp
133
  state=state,
134
  norm_tag=NORM_TAG,
135
  inference_action_mode="continuous",
136
- num_steps=req.num_steps,
137
  normalize_language=True,
138
  enable_cuda_graph=True,
139
  )
 
22
  import base64
23
  import io
24
  import os
25
+ import secrets
26
  import threading
27
  from typing import Optional
28
 
 
38
  AUTH_TOKEN = os.environ.get("NORI_INFER_TOKEN") # REQUIRED — the rollout sends it
39
  # bf16 fits <16GB (A10G/L4). Set MOLMOACT_BF16=0 to run fp32 (~26GB, needs L40S/48GB).
40
  DTYPE = torch.bfloat16 if os.environ.get("MOLMOACT_BF16", "1") == "1" else torch.float32
41
+ # Defensive caps: a valid-token caller can't burn unbounded GPU via a huge solver
42
+ # step count or a flood of images (the endpoint is public, token-gated).
43
+ MAX_NUM_STEPS = 50
44
+ MAX_IMAGES = 6
45
 
46
  app = FastAPI(title="nori-molmoact2")
47
  _model = None
 
121
 
122
  @app.post("/act", response_model=ActResponse)
123
  def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
124
+ # Constant-time compare so a bad token can't be recovered via response timing.
125
+ # Checked BEFORE any model work so unauthenticated calls never touch the GPU.
126
+ if not authorization or not secrets.compare_digest(authorization, f"Bearer {AUTH_TOKEN}"):
127
  raise HTTPException(status_code=401, detail="bad or missing bearer token")
128
  if _model is None:
129
  detail = f"model load failed: {_load_error}" if _load_error else "model not loaded yet"
130
  raise HTTPException(status_code=503, detail=detail)
131
+ if not 1 <= len(req.images) <= MAX_IMAGES:
132
+ raise HTTPException(status_code=422, detail=f"need 1..{MAX_IMAGES} camera images")
133
+ num_steps = max(1, min(int(req.num_steps), MAX_NUM_STEPS)) # clamp GPU cost
134
  images = [_decode(b) for b in req.images]
135
  state = np.asarray(req.state, dtype=np.float32)
136
  with _lock, torch.no_grad():
 
141
  state=state,
142
  norm_tag=NORM_TAG,
143
  inference_action_mode="continuous",
144
+ num_steps=num_steps,
145
  normalize_language=True,
146
  enable_cuda_graph=True,
147
  )