# 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 `