File size: 7,697 Bytes
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e3d5a
 
 
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e3d5a
 
 
39ff632
 
 
 
 
 
 
 
 
 
bf1fb5f
39ff632
 
 
 
 
 
 
 
 
 
 
37e3d5a
 
 
 
 
 
 
 
 
bf1fb5f
 
 
37e3d5a
 
 
 
 
 
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf1fb5f
 
 
 
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e3d5a
 
 
 
39ff632
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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.