Spaces:
Running
Running
| # Security model | |
| This public Space accepts untrusted images, sends normalized image content to a | |
| configured external LLM provider, and renders generated procedural code in a | |
| visitor's browser. The controls and remaining limitations are explicit below. | |
| ## Upload boundary | |
| `app/image_guard.py` and `app/main.py` enforce: | |
| - A 10 MiB default byte cap while reading, before image decode. | |
| - Pillow content sniffing; filename, extension, and declared MIME type are not | |
| treated as proof of format. | |
| - PNG, JPEG, WebP, GIF (first frame), and BMP only. SVG is rejected because it | |
| is scriptable XML. | |
| - Pillow decompression-bomb protection, a 40 MP default pixel cap, and an | |
| 8192 px longest-side decode cap. | |
| - EXIF orientation normalization followed by RGB PNG re-encoding, which strips | |
| original metadata. The normalized image is downscaled to at most 1024 px | |
| before forge/LLM processing. | |
| - Server-generated job IDs and artifact names. The artifact route uses an | |
| explicit filename allowlist and verifies that the resolved file remains | |
| directly inside its job directory. | |
| - Community publication is opt-out and disclosed before submission. It copies | |
| only the normalized reference and the generated artifact allowlist; original | |
| filenames and image metadata are not retained. | |
| The normalized image is sent to the operator-configured LLM endpoint. Users | |
| must treat that provider as a data processor; this Space does not make a local | |
| model privacy claim. | |
| ## Generated code and browser isolation | |
| - The LLM authors JSON spec values, not executable TypeScript. A deterministic | |
| vendored generator emits code and JSON-escapes spec-derived literals. | |
| - The hosted compiler accepts only its implemented primitive allowlist and | |
| rejects parent cycles. Generation fails if a TODO fallback is detected; an | |
| unsupported primitive is never silently represented as a placeholder box. | |
| - The interactive viewer runs in `<iframe sandbox="allow-scripts">` without | |
| `allow-same-origin`. It has an opaque origin and cannot access parent cookies, | |
| storage, or same-origin DOM. Parent/iframe communication is `postMessage`; | |
| the parent validates `event.source`. | |
| - Normal application responses use a restrictive policy including | |
| `default-src 'self'`, `script-src 'self' blob:`, `object-src 'none'`, and | |
| `base-uri 'none'`. `/static/viewer.html` receives a separate explicit CSP | |
| permitting only its required inline bootstrap and blob module import. Its | |
| frame-ancestor list permits this Space itself and the canonical | |
| `https://huggingface.co` embedding page, but no arbitrary third-party origin. | |
| - Generated artifacts are served as files. Standalone HTML base64-embeds the | |
| bundle, creates a blob URL, and accesses module exports through | |
| `await import(url)` rather than assuming export aliases are local bindings. | |
| Browser sandboxing limits access to the Space origin; it is not a proof that | |
| arbitrary generated code is harmless in every browser implementation. Keep the | |
| sandbox and CSP in place when embedding or modifying the viewer. | |
| ## Server-side process boundary | |
| Forge, esbuild, and the isolated gallery publisher run with: | |
| - list-form argv and `shell=False`; | |
| - per-call timeouts and bounded captured output; | |
| - a scrubbed child environment that omits `LLM_*` and `ANTHROPIC_*` variables; | |
| - server-controlled files inside a per-job temporary directory. | |
| The strict-validated original spec remains locked and unreviewed. A deep-copied | |
| `hosted-unreviewed-preview` manifest is used only to compile all supported | |
| components for inspection. It carries no reviewer, screenshot, comparison | |
| image, score, or upstream `continue` decision. | |
| ## Persistent community gallery | |
| - The private Hugging Face Bucket is mounted read/write at `/data`; the app | |
| serves published entries through strict gallery routes rather than exposing | |
| the Bucket directly. | |
| - An opted-out job and every failed job stay in ephemeral job storage and are | |
| never published. | |
| - Publication copies to a staging directory and makes the completed item | |
| visible only after its metadata manifest is atomically committed. | |
| - Bucket copying runs in a scrubbed child process with a separate bounded | |
| deadline. Cancellation terminates and reaps that process, so a stalled mount | |
| cannot hold a conversion slot indefinitely. | |
| - Gallery IDs use the same full-entropy server-generated format as jobs. | |
| Listing ignores malformed/incomplete entries. Artifact routes enforce an | |
| explicit allowlist plus direct-parent containment. | |
| - Gallery entries intentionally persist across app restarts. Users should not | |
| upload images they are unwilling to publish while the share toggle is on. | |
| ## Secrets and deployment | |
| - Runtime credentials are read from environment variables at startup and are | |
| never logged or returned by `/api/config`. | |
| - Hugging Face injects Space Secrets at runtime; no credential is a Docker | |
| build argument or image layer. | |
| - `scripts/deploy_space.py` uses the authenticated local `hf` CLI store, stages | |
| a deterministic file allowlist, and performs one upload from that sanitized | |
| directory. | |
| - Environment-derived secret values are written only to a temporary mode-0600 | |
| secrets file, passed via `hf spaces secrets add --secrets-file`, and removed | |
| in a `finally` block. Logs and process arguments contain secret names only. | |
| ## Abuse and resource control | |
| - The default per-client limit is 10 accepted jobs per hour with | |
| `Retry-After`. The in-memory client map has an LRU size bound. | |
| - Client identity comes from the ASGI socket peer as resolved by the trusted | |
| Uvicorn proxy configuration. Application code does not reinterpret a raw, | |
| caller-controlled `X-Forwarded-For` header. | |
| - Two jobs run concurrently by default and queued plus running jobs are capped | |
| at eight. A full queue returns `queue_full` with `Retry-After`; queued uploads | |
| therefore cannot pin unbounded memory. | |
| - The conversion deadline starts when a job is accepted, includes queue time, | |
| and ends only after the browser bundle is complete. Queue, model, generator, | |
| bundler, and publisher waits emit elapsed-only feedback rather than invented | |
| percentages. Optional Bucket publication has its own shorter deadline. | |
| - Provider retries apply only to transient statuses, not 400/401/403. | |
| - Terminal jobs have a two-hour default TTL. The reaper does not delete a | |
| running job solely because it was created a long time ago. | |
| All abuse controls are process-local. They reduce accidental/public-demo load; | |
| they are not a distributed quota system. | |
| ## Container boundary | |
| - The service runs as non-root UID 1000 under `tini` on | |
| `python:3.12-slim-bookworm` and binds to `0.0.0.0:7860`. | |
| - The final Docker stage copies only runtime dependencies, `app/`, required | |
| `forge/` code, and `LICENSE`. | |
| - `.dockerignore`, the runtime COPY allowlist, and Docker verification exclude | |
| VCS state, virtual environments, rollouts, caches, tests, scripts, | |
| `upstream-src`, and local verification artifacts from the final image. | |
| - Rebuild the image regularly to pick up base-image security updates. | |
| ## Accepted limitations | |
| - A single image does not reveal hidden geometry. Results are approximate and | |
| hidden sides are inferred, not observed or measured. | |
| - A strict-valid spec is not visually approved. The hosted result is explicitly | |
| unreviewed; production use requires the upstream screenshot/comparison review | |
| loop. | |
| - Opted-out/failed job artifacts live in ephemeral `/tmp` and expire. Their | |
| hard-to-guess URLs are unlisted, not access-controlled. Published | |
| gallery entries persist in the mounted Bucket; this release does not provide | |
| a public deletion or moderation API. | |
| - Rate limiting and job state reset when the single process restarts. | |