# Security Hardening Review Review date: 2026-06-14. Reviewer: Claude (Opus 4.8), for OpenAI Codex to implement. Final submission note, 2026-06-15: this review was implemented before the Space became public under `build-small-hackathon/Snap2Sim`. This review answers three questions: 1. Are the endpoints secure? 2. Is the UI/UX good? 3. Should we prioritize Three.js or A-Frame for animation rendering? Two product decisions were confirmed with the owner and shape the priorities below: - **The Space is public now.** Findings that were only mitigated by private visibility were treated as blockers and were resolved before publication. - **Screen-only viewing; no VR/AR goal.** WebXR is not required, which removes A-Frame's main structural advantage. File references use `path:line` from the state of the repo at review time. --- ## 1. Endpoint Security ### Architecture summary - `app.py` is the public Hugging Face Space (`gradio.Server`). It serves `index.html` at `/` and exposes `POST /analyze_image` and `POST /generate_scene`. The browser calls these **same-origin**, and the server attaches the Modal bearer token when it forwards to Modal. This split (token stays server-side) is correct and matches `SECURITY.md`. - `modal_app.py` exposes the GPU/inference web endpoints, each guarded by a bearer token via `require_authorization`. The Modal layer is in good shape. The Space layer is the exposure. ### HIGH — The Space's own `/analyze_image` and `/generate_scene` are unauthenticated (`app.py:64`, `app.py:74`) These endpoints have no auth of their own. At review time, they were gated only by the Space being private. **Because the Space is now public, anyone on the internet can POST to them unless the implemented abuse controls remain active.** - With `INFERENCE_BACKEND=local` (current default), they return placeholder data — low impact but a free compute/bandwidth sink (base64 decode + PIL decode + scene build per request). - With `INFERENCE_BACKEND=modal`, every public request is proxied to Modal **using the server's own bearer token**, turning the Space into an open proxy that spends GPU credits on behalf of any anonymous caller. The Modal bearer token protects Modal from direct callers, but the Space is a trusted caller, so the token does not help here. This directly contradicts the stated intent in `SECURITY.md` that the token is "the real protection against credit-spending spam." **Recommendation (implemented before publication):** - Add abuse controls at the Space layer that do not require leaking a secret to the browser. Options, roughly in order of preference: 1. **Server-side rate limiting / quota** per client IP and a global ceiling (e.g. `slowapi`/`limits`, or a small in-process token bucket). This is the most important mitigation since the Space is intentionally token-free on the client side. 2. A short-lived, server-issued session/CSRF-style token handed to the page on `GET /` and required on the POST endpoints, to stop trivial scripted abuse (not a strong control, but raises the bar). 3. Hard caps: max upload size, max requests/min, and a circuit breaker that falls back to `local` placeholder mode if a Modal spend threshold is hit. - Consider HF Space-level protections too (HF supports gating), but do not rely on them as the only layer once public. ### HIGH — Untrusted scene HTML is injected via `innerHTML` (`index.html:545`) `renderAframe()` does `viewport.innerHTML = sceneHtml;` where `sceneHtml` comes from `/generate_scene`. In the deterministic path the HTML is built server-side with `html.escape` (`snap2sim/aframe_scene.py:14-16`, safe). But the model-generation path (`generate_scene_llamacpp` in `modal_app.py:462`) returns **raw model output**, extracted by `parse_scene_response` (`snap2sim/model_io.py:49`) which does *no sanitization* — it slices everything between `` and `` verbatim. Because the scene is derived from a **user-uploaded image**, an attacker can prompt-inject the vision/scene model (text in the photo, adversarial content) into emitting markup such as `` or ``. `innerHTML` will not execute injected `