Repository navigation
Scene permissions: apps declare, scenes add up, the frame enforces - #556
Conversation
…ault
A scene installed from the scene store is anyone's code, whichever control
plane installed it. Until now it read and wrote the whole of /srv/assets
through the JS asset calls (fonts, other scenes' files, .frameos/scene_images)
and `data/localImage`'s `path` named any folder on disk. The per-scene asset
sandbox existed but was opt-in (`js.assetSandbox: "scene"`).
One module, frameos/asset_scope.nim, now answers "which folder is this
scene's" for every consumer of a scene-supplied path: the JS asset API,
`saveAsset`, and the localImage app. The rule keys on the same
`storeOrigin` bit the shell-apps gate uses:
"store" (new default): store-origin scenes get <assets>/scenes/<sceneId>/
and nothing else; the owner's scenes share the frame's folder
"scene": every scene its own subtree
"frame": nothing confined (the old behavior)
The containment check (lexical, then against the real path so a planted
symlink is not a way out) moved there unchanged. For a confined scene,
localImage accepts an empty or relative path inside its subtree, or an
absolute one already pointing into it, and paints a refusal for anything
else. The wasm preview confines nothing under "store": it is the author's
own machine. An unknown value falls back to "store", never to open.
Tests: test_asset_scope (new), test_js_app_runtime (store vs owner under
the default, "frame" switches it off), localImage test_app (refusal, the
three accepted path shapes, the owner's scene unchanged),
test_config_helpers (new default). Docs: scene guide (regenerated copies),
js-apps doc, cloud-frames.md posture; the security-todo item is deleted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ESP32 firmware sizeBuilt from 0411dbb by this run; compared with the latest release v2026.10.15.
Breakdown by subsystem —
|
| Subsystem | This PR | v2026.10.15 | Δ |
|---|---|---|---|
| FrameOS core (Nim) | 479,344 | 477,042 | +2,302 (+0.5%) |
| QuickJS | 360,413 | 360,373 | +40 (+0.0%) |
| pixie | 304,418 | 304,354 | +64 (+0.0%) |
| Wi-Fi stack | 295,304 | 295,304 | ±0 |
| Nim stdlib | 251,555 | 251,436 | +119 (+0.0%) |
| FrameOS apps (Nim) | 190,448 | 190,356 | +92 (+0.0%) |
| ESP-IDF misc | 181,371 | 181,564 | -193 (-0.1%) |
| Embedded font | 146,362 | 146,362 | ±0 |
| String pool (attributed to efuse) | 135,840 | 136,134 | -294 (-0.2%) |
| fos_* firmware shell (C) | 133,174 | 132,233 | +941 (+0.7%) |
| mbedTLS + certificates | 132,439 | 132,439 | ±0 |
| lwIP / HTTP / WebSocket | 126,792 | 126,800 | -8 (-0.0%) |
| libc / libm / newlib | 116,263 | 116,263 | ±0 |
| Storage (SPIFFS / FatFS / SD / NVS) | 94,076 | 94,080 | -4 (-0.0%) |
| Nim packages (chrono, zippy, chroma, qrgen, ...) | 71,845 | 71,849 | -4 (-0.0%) |
| Display drivers (C) | 64,958 | 64,946 | +12 (+0.0%) |
| Crypto (monocypher) | 35,141 | 35,141 | ±0 |
| Total mapped flash | 3,119,743 | 3,116,676 | +3,067 (+0.1%) |
Inside each subsystem
FrameOS core (Nim) — 479,344
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
frameos/js_runtime |
116,841 | 115,444 | +1,397 (+1.2%) |
frameos/utils |
112,365 | 112,401 | -36 (-0.0%) |
frameos/interpreter.nim |
100,260 | 100,264 | -4 (-0.0%) |
embedded_main.nim |
19,252 | 19,252 | ±0 |
embedded_runtime.nim |
17,830 | 17,834 | -4 (-0.0%) |
lib/tz.nim |
16,722 | 16,722 | ±0 |
frameos_nim_glue.c.obj |
10,679 | 10,635 | +44 (+0.4%) |
frameos/planner.nim |
9,689 | 9,681 | +8 (+0.1%) |
frameos/event_loop.nim |
8,412 | 8,412 | ±0 |
frameos/types.nim |
7,909 | 7,909 | ±0 |
frameos/values.nim |
7,699 | 7,699 | ±0 |
frameos/single_scene_host.nim |
6,687 | 6,712 | -25 (-0.4%) |
frameos/apps.nim |
6,585 | 6,585 | ±0 |
frameos/spool.nim |
5,787 | 5,787 | ±0 |
frameos/input_state.nim |
4,947 | 3,766 | +1,181 (+31.4%) |
embedded_scene.nim |
4,945 | 4,945 | ±0 |
frameos/events_gen.nim |
3,948 | 3,948 | ±0 |
frameos/app_config.nim |
2,951 | 2,935 | +16 (+0.5%) |
frameos/reboot_reason.nim |
2,648 | 2,648 | ±0 |
frameos/node_config.nim |
2,618 | 2,618 | ±0 |
frameos/app_capabilities.nim |
2,489 | 2,489 | ±0 |
frameos/events.nim |
2,410 | 2,410 | ±0 |
fos_netguard.c.obj |
2,179 | 2,179 | ±0 |
frameos/refresh_interval.nim |
1,635 | 1,639 | -4 (-0.2%) |
frameos/runtime_diagnostics.nim |
767 | 767 | ±0 |
frameos/event_log.nim |
415 | 431 | -16 (-3.7%) |
fos_version.c.obj |
328 | 328 | ±0 |
frameos/cloud |
— | 255 | -255 (-100.0%) |
frameos/channels.nim |
152 | 152 | ±0 |
frameos/ids.nim |
137 | 137 | ±0 |
frameos/hal |
44 | 44 | ±0 |
frameos/retired_apps.nim |
14 | 14 | ±0 |
QuickJS — 360,413
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
quickjs.c.obj |
287,886 | 287,846 | +40 (+0.0%) |
libunicode.c.obj |
50,178 | 50,178 | ±0 |
libregexp.c.obj |
13,999 | 13,999 | ±0 |
dtoa.c.obj |
5,685 | 5,685 | ±0 |
cutils.c.obj |
2,141 | 2,141 | ±0 |
fos_qjs_glue.c.obj |
449 | 449 | ±0 |
fos_quickjs_tz.c.obj |
75 | 75 | ±0 |
pixie — 304,418
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
pixie/fontformats/opentype.nim |
55,460 | 55,460 | ±0 |
pixie/fileformats/webp.nim |
37,493 | 37,493 | ±0 |
pixie/fileformats/svg.nim |
36,983 | 36,983 | ±0 |
pixie/paths.nim |
32,875 | 32,875 | ±0 |
pixie/fileformats/jpeg.nim |
29,440 | 29,436 | +4 (+0.0%) |
pixie/fileformats/png.nim |
22,101 | 22,101 | ±0 |
pixie/images.nim |
15,779 | 15,779 | ±0 |
pixie/fonts.nim |
10,995 | 10,995 | ±0 |
pixie/paints.nim |
9,869 | 9,869 | ±0 |
pixie/fileformats/bmp.nim |
8,278 | 8,258 | +20 (+0.2%) |
pixie/common.nim |
7,417 | 7,413 | +4 (+0.1%) |
pixie/blends.nim |
6,516 | 6,516 | ±0 |
pixie/fileformats/gif.nim |
5,961 | 5,965 | -4 (-0.1%) |
pixie/inflatestream.nim |
5,233 | 5,233 | ±0 |
pixie.nim |
5,224 | 5,224 | ±0 |
pixie/fileformats/webp_vp8_tables.nim |
3,881 | 3,881 | ±0 |
pixie/fileformats/ppm.nim |
3,725 | 3,713 | +12 (+0.3%) |
pixie/contexts.nim |
3,058 | 3,054 | +4 (+0.1%) |
pixie/fileformats/qoi.nim |
2,726 | 2,702 | +24 (+0.9%) |
pixie/internal.nim |
754 | 754 | ±0 |
pixie/rgb565.nim |
385 | 385 | ±0 |
pixie/fontformats/svgfont.nim |
215 | 215 | ±0 |
pixie/decodebudget.nim |
50 | 50 | ±0 |
Wi-Fi stack — 295,304
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libnet80211.a |
136,168 | 136,168 | ±0 |
libpp.a |
62,154 | 62,154 | ±0 |
libwpa_supplicant.a |
54,904 | 54,904 | ±0 |
libphy.a |
34,176 | 34,176 | ±0 |
libesp_wifi.a |
4,780 | 4,780 | ±0 |
libesp_phy.a |
2,541 | 2,541 | ±0 |
libesp_coex.a |
291 | 291 | ±0 |
libcore.a |
287 | 287 | ±0 |
libespnow.a |
3 | 3 | ±0 |
Nim stdlib — 251,555
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
pure/collections/tables.nim |
72,312 | 72,312 | ±0 |
system.nim |
48,485 | 48,457 | +28 (+0.1%) |
pure/unicode.nim |
17,405 | 17,405 | ±0 |
pure/times.nim |
16,888 | 16,888 | ±0 |
pure/json.nim |
13,726 | 13,722 | +4 (+0.0%) |
std/private/dragonbox.nim |
10,351 | 10,351 | ±0 |
pure/strutils.nim |
8,047 | 8,047 | ±0 |
pure/parsexml.nim |
6,003 | 6,011 | -8 (-0.1%) |
pure/algorithm.nim |
5,372 | 5,372 | ±0 |
pure/base64.nim |
5,290 | 5,290 | ±0 |
pure/hashes.nim |
4,181 | 4,181 | ±0 |
pure/xmlparser.nim |
3,555 | 3,555 | ±0 |
pure/parsejson.nim |
3,553 | 3,553 | ±0 |
std/formatfloat.nim |
3,504 | 3,504 | ±0 |
pure/uri.nim |
3,225 | 3,225 | ±0 |
pure/streams.nim |
2,820 | 2,820 | ±0 |
pure/strformat.nim |
2,546 | 2,546 | ±0 |
std/syncio.nim |
2,355 | 2,355 | ±0 |
pure/collections/sets.nim |
2,075 | 2,075 | ±0 |
pure/xmltree.nim |
2,047 | 2,047 | ±0 |
pure/lexbase.nim |
1,801 | 1,801 | ±0 |
pure/parseutils.nim |
1,750 | 1,750 | ±0 |
system/exceptions.nim |
1,598 | 1,598 | ±0 |
pure/strtabs.nim |
1,572 | 1,572 | ±0 |
pure/pathnorm.nim |
1,336 | 1,336 | ±0 |
pure/options.nim |
1,016 | 1,016 | ±0 |
std/private/ospaths2.nim |
994 | 994 | ±0 |
pure/collections/lists.nim |
979 | 979 | ±0 |
pure/math.nim |
841 | 841 | ±0 |
pure/collections/deques.nim |
822 | 822 | ±0 |
pure/random.nim |
696 | 696 | ±0 |
std/private/digitsutils.nim |
651 | 651 | ±0 |
std/oserrors.nim |
633 | 633 | ±0 |
std/private/osdirs.nim |
572 | 572 | ±0 |
system/dollars.nim |
529 | 529 | ±0 |
std/monotimes.nim |
501 | 406 | +95 (+23.4%) |
std/private/oscommon.nim |
329 | 329 | ±0 |
std/envvars.nim |
250 | 250 | ±0 |
std/private/decode_helpers.nim |
240 | 240 | ±0 |
std/private/ossymlinks.nim |
187 | 187 | ±0 |
pure/bitops.nim |
162 | 162 | ±0 |
pure/os.nim |
99 | 99 | ±0 |
std/private/osfiles.nim |
96 | 96 | ±0 |
std/assertions.nim |
87 | 87 | ±0 |
core/macros.nim |
65 | 65 | ±0 |
system/ctypes.nim |
9 | 9 | ±0 |
FrameOS apps (Nim) — 190,448
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
apps/data/icalJson |
50,737 | 50,733 | +4 (+0.0%) |
apps/render/calendar |
30,894 | 30,874 | +20 (+0.1%) |
apps/data/localImage |
10,432 | 10,432 | ±0 |
apps/render/split |
9,736 | 9,772 | -36 (-0.4%) |
apps/data/eventsToAgenda |
9,068 | 9,068 | ±0 |
apps/render/zoomPan |
8,676 | 8,648 | +28 (+0.3%) |
apps/apps.nim |
7,251 | 7,247 | +4 (+0.1%) |
apps/render/text |
5,491 | 5,455 | +36 (+0.7%) |
apps/data/xmlToJson |
5,428 | 5,428 | ±0 |
apps/render/svg |
5,244 | 5,192 | +52 (+1.0%) |
apps/render/image |
4,724 | 4,708 | +16 (+0.3%) |
apps/data/rotateImage |
3,465 | 3,465 | ±0 |
apps/logic/setAsState |
3,453 | 3,453 | ±0 |
apps/data/downloadImage |
3,317 | 3,325 | -8 (-0.2%) |
apps/render/gradient |
3,217 | 3,229 | -12 (-0.4%) |
apps/data/qr |
3,118 | 3,122 | -4 (-0.1%) |
apps/data/downloadUrl |
3,053 | 3,053 | ±0 |
apps/render/color |
2,840 | 2,824 | +16 (+0.6%) |
apps/data/newImage |
2,668 | 2,668 | ±0 |
apps/data/resizeImage |
2,558 | 2,558 | ±0 |
apps/data/log |
2,299 | 2,299 | ±0 |
apps/render/opacity |
2,288 | 2,296 | -8 (-0.3%) |
apps/data/prettyJson |
2,225 | 2,225 | ±0 |
apps/data/clock |
1,871 | 1,875 | -4 (-0.2%) |
apps/data/parseJson |
1,848 | 1,856 | -8 (-0.4%) |
apps/logic/nextSleepDuration |
1,663 | 1,663 | ±0 |
apps/logic/ifElse |
1,494 | 1,494 | ±0 |
apps/logic/breakIfRendering |
1,390 | 1,394 | -4 (-0.3%) |
ESP-IDF misc — 181,371
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libesp_hw_support.a |
37,640 | 37,632 | +8 (+0.0%) |
libhal.a |
21,865 | 21,865 | ±0 |
libfreertos.a |
17,240 | 17,240 | ±0 |
libesp_system.a |
13,736 | 13,780 | -44 (-0.3%) |
libesp_driver_uart.a |
11,627 | 11,627 | ±0 |
libheap.a |
8,660 | 8,660 | ±0 |
libesp_driver_i2c.a |
8,062 | 8,062 | ±0 |
libesp_driver_spi.a |
7,756 | 7,756 | ±0 |
libconsole.a |
5,733 | 5,733 | ±0 |
libjson.a |
4,940 | 4,940 | ±0 |
libesp_driver_gpio.a |
3,878 | 3,878 | ±0 |
libbootloader_support.a |
3,813 | 3,813 | ±0 |
libxtensa.a |
3,691 | 3,691 | ±0 |
libesp_driver_usb_serial_jtag.a |
3,408 | 3,408 | ±0 |
libesp_ringbuf.a |
3,322 | 3,322 | ±0 |
libesp_event.a |
3,253 | 3,253 | ±0 |
libesp_driver_ledc.a |
3,054 | 3,054 | ±0 |
libesp_mm.a |
2,451 | 2,451 | ±0 |
libapp_update.a |
2,414 | 2,414 | ±0 |
libesp_timer.a |
2,337 | 2,337 | ±0 |
libesp_psram.a |
2,172 | 2,329 | -157 (-6.7%) |
libsoc.a |
2,316 | 2,316 | ±0 |
libesp_adc.a |
2,285 | 2,285 | ±0 |
libpthread.a |
1,397 | 1,397 | ±0 |
liblog.a |
1,375 | 1,375 | ±0 |
libesp_security.a |
1,050 | 1,050 | ±0 |
libesp_rom.a |
902 | 902 | ±0 |
libesp_app_format.a |
507 | 507 | ±0 |
libxt_hal.a |
437 | 437 | ±0 |
libesp_common.a |
26 | 26 | ±0 |
libesp_pm.a |
24 | 24 | ±0 |
Embedded font — 146,362
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
assets/fonts.nim |
146,362 | 146,362 | ±0 |
String pool (attributed to efuse) — 135,840
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
esp_efuse_utility.c.obj |
134,794 | 135,088 | -294 (-0.2%) |
esp_efuse_rtc_calib.c.obj |
566 | 566 | ±0 |
esp_efuse_table.c.obj |
268 | 268 | ±0 |
esp_efuse_api.c.obj |
151 | 151 | ±0 |
esp_efuse_startup.c.obj |
61 | 61 | ±0 |
fos_ firmware shell (C)* — 133,174
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
fos_http.c.obj |
22,185 | 22,185 | ±0 |
fos_cloud.c.obj |
19,185 | 19,258 | -73 (-0.4%) |
fos_console.c.obj |
13,950 | 13,950 | ±0 |
fos_client.c.obj |
12,350 | 12,266 | +84 (+0.7%) |
fos_settings.c.obj |
7,618 | 7,618 | ±0 |
fos_scenes.c.obj |
7,515 | 7,578 | -63 (-0.8%) |
fos_ota.c.obj |
5,467 | 5,467 | ±0 |
fos_cloud_contract.c.obj |
4,744 | 4,744 | ±0 |
fos_config.c.obj |
4,619 | 4,619 | ±0 |
fos_wifi.c.obj |
3,189 | 3,189 | ±0 |
fos_assets.c.obj |
3,124 | 3,124 | ±0 |
fos_buttons.c.obj |
2,772 | 2,493 | +279 (+11.2%) |
fos_sd_probe.c.obj |
2,753 | 2,753 | ±0 |
fos_schedule.c.obj |
2,602 | 2,602 | ±0 |
fos_assets_sd.c.obj |
2,484 | 2,484 | ±0 |
fos_events.c.obj |
2,320 | 2,320 | ±0 |
fos_status_screen.c.obj |
2,049 | 2,049 | ±0 |
main.c.obj |
1,703 | 1,699 | +4 (+0.2%) |
fos_config_parse.c.obj |
1,467 | 1,467 | ±0 |
fos_url_guard.c.obj |
1,397 | 1,397 | ±0 |
fos_tz.c.obj |
1,294 | 1,294 | ±0 |
fos_buzzer.c.obj |
1,162 | 901 | +261 (+29.0%) |
fos_battery.c.obj |
1,140 | 1,140 | ±0 |
fos_minisig.c.obj |
1,140 | 1,140 | ±0 |
fos_backend_http.c.obj |
1,072 | 1,072 | ±0 |
fos_commands.c.obj |
940 | 940 | ±0 |
fos_scene_state.c.obj |
650 | 650 | ±0 |
fos_framebuffer.c.obj |
483 | 483 | ±0 |
fos_button_timing.c.obj |
434 | — | +434 |
fos_board.c.obj |
334 | 334 | ±0 |
fos_assets_path.c.obj |
259 | 259 | ±0 |
fos_power.c.obj |
241 | 241 | ±0 |
fos_battery_filter.c.obj |
214 | 214 | ±0 |
fos_wake.c.obj |
199 | 184 | +15 (+8.2%) |
fos_json_guard.c.obj |
119 | 119 | ±0 |
mbedTLS + certificates — 132,439
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libmbedcrypto.a |
71,295 | 71,291 | +4 (+0.0%) |
libmbedtls.a |
45,799 | 45,799 | ±0 |
libmbedx509.a |
7,088 | 7,092 | -4 (-0.1%) |
libesp-tls.a |
6,705 | 6,705 | ±0 |
libesp_https_server.a |
1,552 | 1,552 | ±0 |
lwIP / HTTP / WebSocket — 126,792
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
liblwip.a |
78,437 | 78,445 | -8 (-0.0%) |
libhttp_parser.a |
10,350 | 10,350 | ±0 |
libesp_http_server.a |
10,020 | 10,020 | ±0 |
libtcp_transport.a |
8,245 | 8,245 | ±0 |
libespressif__esp_websocket_client.a |
6,822 | 6,822 | ±0 |
libesp_http_client.a |
6,666 | 6,666 | ±0 |
libesp_netif.a |
6,252 | 6,252 | ±0 |
libc / libm / newlib — 116,263
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libc.a |
76,471 | 76,471 | ±0 |
libm.a |
31,563 | 31,563 | ±0 |
libnewlib.a |
6,609 | 6,609 | ±0 |
libstdc++.a |
1,476 | 1,476 | ±0 |
libgcc.a |
98 | 98 | ±0 |
libcxx.a |
46 | 46 | ±0 |
Storage (SPIFFS / FatFS / SD / NVS) — 94,076
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libspiffs.a |
20,380 | 20,380 | ±0 |
libfatfs.a |
20,133 | 20,133 | ±0 |
libspi_flash.a |
14,168 | 14,168 | ±0 |
libnvs_flash.a |
13,681 | 13,685 | -4 (-0.0%) |
libsdmmc.a |
13,013 | 13,013 | ±0 |
libvfs.a |
6,196 | 6,196 | ±0 |
libesp_driver_sdspi.a |
3,866 | 3,866 | ±0 |
libesp_partition.a |
1,956 | 1,956 | ±0 |
libesp_vfs_console.a |
678 | 678 | ±0 |
libnvs_sec_provider.a |
5 | 5 | ±0 |
Nim packages (chrono, zippy, chroma, qrgen, ...) — 71,845
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
QRgen 3.1.0 |
22,056 | 22,060 | -4 (-0.0%) |
chrono 0.3.1 |
15,586 | 15,586 | ±0 |
zippy 0.10.19 |
15,200 | 15,200 | ±0 |
chroma 1.0.0 |
14,714 | 14,714 | ±0 |
checksums 0.2.1 |
3,371 | 3,371 | ±0 |
flatty 0.3.4 |
533 | 533 | ±0 |
jsony 1.1.5 |
272 | 272 | ±0 |
bumpy 1.1.3 |
113 | 113 | ±0 |
Display drivers (C) — 64,958
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
(everything else) |
21,278 | 21,282 | -4 (-0.0%) |
frameos_panel_table.c.obj |
6,685 | 6,685 | ±0 |
EPD_13in3e.c.obj |
1,796 | 1,796 | ±0 |
EPD_3in7.c.obj |
1,564 | 1,564 | ±0 |
EPD_5in79.c.obj |
1,481 | 1,481 | ±0 |
DEV_Debug.c.obj |
1,325 | 1,317 | +8 (+0.6%) |
EPD_2in7.c.obj |
1,292 | 1,292 | ±0 |
EPD_4in2.c.obj |
1,262 | 1,262 | ±0 |
EPD_7in3e.c.obj |
1,208 | 1,208 | ±0 |
EPD_4in2_V2.c.obj |
1,184 | 1,184 | ±0 |
DEV_Config_esp.c.obj |
1,180 | 1,180 | ±0 |
EPD_4in26.c.obj |
1,123 | 1,123 | ±0 |
| 29 smaller rows | 23,580 | 23,572 | +8 (+0.0%) |
Crypto (monocypher) — 35,141
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
monocypher.c.obj |
31,916 | 31,916 | ±0 |
monocypher-ed25519.c.obj |
3,225 | 3,225 | ±0 |
Biggest movers (per object)
| Object | Δ | This PR | Baseline |
|---|---|---|---|
frameos/input_state.nim |
+4,947 | 4,947 | — |
pkgs/QRgen-3.1.0/QRgen/renderer.nim |
-3,780 | — | 3,780 |
frameos/js_runtime/app_runtime.nim |
+1,441 | 74,076 | 72,635 |
esp_efuse_utility.c.obj |
-294 | 134,794 | 135,088 |
fos_client.c.obj |
+84 | 12,350 | 12,266 |
fos_cloud.c.obj |
-73 | 19,185 | 19,258 |
fos_scenes.c.obj |
-63 | 7,515 | 7,578 |
frameos/js_runtime/runtime.nim |
-48 | 31,638 | 31,686 |
frameos_nim_glue.c.obj |
+44 | 10,679 | 10,635 |
quickjs.c.obj |
+40 | 287,886 | 287,846 |
apps/render/split/app_loader.nim |
-32 | 4,670 | 4,702 |
nim/lib/system.nim |
+28 | 48,485 | 48,457 |
frameos/single_scene_host.nim |
-25 | 6,687 | 6,712 |
pkgs/pixie-6.1.0/pixie/fileformats/bmp.nim |
+20 | 8,278 | 8,258 |
apps/render/calendar/app_loader.nim |
+20 | 7,077 | 7,057 |
Largest 30 objects
| Object | Subsystem | This PR | v2026.10.15 | Δ |
|---|---|---|---|---|
quickjs.c.obj |
QuickJS | 287,886 | 287,846 | +40 (+0.0%) |
assets/fonts.nim |
Embedded font | 146,362 | 146,362 | ±0 |
esp_efuse_utility.c.obj |
String pool (attributed to efuse) | 134,794 | 135,088 | -294 (-0.2%) |
frameos/interpreter.nim |
FrameOS core (Nim) | 100,260 | 100,264 | -4 (-0.0%) |
frameos/js_runtime/app_runtime.nim |
FrameOS core (Nim) | 74,076 | 72,635 | +1,441 (+2.0%) |
nim/lib/pure/collections/tables.nim |
Nim stdlib | 72,312 | 72,312 | ±0 |
pkgs/pixie-6.1.0/pixie/fontformats/opentype.nim |
pixie | 55,460 | 55,460 | ±0 |
libunicode.c.obj |
QuickJS | 50,178 | 50,178 | ±0 |
nim/lib/system.nim |
Nim stdlib | 48,485 | 48,457 | +28 (+0.1%) |
apps/data/icalJson/ical.nim |
FrameOS apps (Nim) | 45,020 | 45,016 | +4 (+0.0%) |
pkgs/pixie-6.1.0/pixie/fileformats/webp.nim |
pixie | 37,493 | 37,493 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/svg.nim |
pixie | 36,983 | 36,983 | ±0 |
frameos/utils/image.nim |
FrameOS core (Nim) | 35,942 | 35,942 | ±0 |
pkgs/pixie-6.1.0/pixie/paths.nim |
pixie | 32,875 | 32,875 | ±0 |
monocypher.c.obj |
Crypto (monocypher) | 31,916 | 31,916 | ±0 |
frameos/js_runtime/runtime.nim |
FrameOS core (Nim) | 31,638 | 31,686 | -48 (-0.2%) |
pkgs/pixie-6.1.0/pixie/fileformats/jpeg.nim |
pixie | 29,440 | 29,436 | +4 (+0.0%) |
apps/render/calendar/app.nim |
FrameOS apps (Nim) | 23,817 | 23,817 | ±0 |
fos_http.c.obj |
fos_* firmware shell (C) | 22,185 | 22,185 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/png.nim |
pixie | 22,101 | 22,101 | ±0 |
embedded_main.nim |
FrameOS core (Nim) | 19,252 | 19,252 | ±0 |
fos_cloud.c.obj |
fos_* firmware shell (C) | 19,185 | 19,258 | -73 (-0.4%) |
frameos/utils/status_screen.nim |
FrameOS core (Nim) | 17,939 | 17,943 | -4 (-0.0%) |
x509_crt_bundle.S.obj |
mbedTLS + certificates | 17,928 | 17,928 | ±0 |
embedded_runtime.nim |
FrameOS core (Nim) | 17,830 | 17,834 | -4 (-0.0%) |
nim/lib/pure/unicode.nim |
Nim stdlib | 17,405 | 17,405 | ±0 |
frameos/utils/asset_colors.nim |
FrameOS core (Nim) | 17,333 | 17,333 | ±0 |
nim/lib/pure/times.nim |
Nim stdlib | 16,888 | 16,888 | ±0 |
lib/tz.nim |
FrameOS core (Nim) | 16,722 | 16,722 | ±0 |
pkgs/pixie-6.1.0/pixie/images.nim |
pixie | 15,779 | 15,779 | ±0 |
Flash = .text + .rodata from the linker map via esp_idf_size; "Inside each subsystem" splits every bucket one level down (the Nim core by directory, apps by app, packages by package, ESP-IDF by archive) over all objects, not just the largest. "String pool (attributed to efuse)" is the linker's merged string-literal pool for the whole image, not efuse code — see docs/esp32-image-size.md.
Store provenance (`storeOrigin`) is hard to keep true across copies, forks and imports, so it no longer decides what a scene may read. Instead: - `settings.assetAccess` on the scene: "auto" (default), "cache-home-only" (the scene's own <assets>/scenes/<id>/ for asset calls, saveAsset and Local Image), "all-assets" (Local Image held inside the assets folder) or "all-files". The frame enforces the explicit values through asset_scope.nim; "auto" runs as before. `js.assetSandbox` goes back to "frame" (default) / "scene", the frame owner's ceiling. - One detector, frontend/src/utils/sceneAccess.ts, reads what a scene reaches for from its JSON (asset calls, Local Image paths, shell apps) and gives the flags "runs shell commands", "reads assets folder", "reads all files". The SPA shows them on scene rows, the scene drawer, template and store tiles, and asks before a URL install; the cloud store computes risk_flags at publish with the same function (shell detection moved there too), and its scene lint checks the setting. Corpus: docs/scene-access-fixtures.json. - Scene settings gain "Access to assets", whose Auto option says what was detected. - A non-string settings.assetAccess no longer fails the scenes.json parse. The shell-apps gate (spawn_guard.nim) still keys on storeOrigin, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scene:backfill-risk-flags recomputes store_scene_versions.risk_flags (and the latest version's copy on store_scenes) from each version's own scenes, so versions published before the assets/files flags show them. Dry run by default. Builtin apps catalog: the Local Image source it embeds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A store stamp (origin.storeSceneId) is not something the frame can keep true across copies, forks and imports, so the frame no longer reads it: - spawn_guard.nim: a store-stamped scene no longer needs the on-panel "shellApps" ceremony to run chromiumScreenshot / rstpSnapshot. A scene that uses either app says so in its own JSON, every surface shows "runs shell commands", and installing it is the approval. The URL checks on what the child process opens stay, for every scene; cloud-managed frames still refuse both apps. The shellApps scope now only lets Chromium run unsandboxed where FrameOS runs as root. - The private-network deny is armed by the installer (cloud-managed frame, cloud-pushed scenes), no longer by a store scene being resident, on Linux (hub_client.nim) and ESP32 (fos_cloud.c, fos_scenes.c). - storeOrigin, storeOriginScenesResident, scenePayloadHasStoreOrigin and sceneOriginIsStore are gone. docs/security-todo.md gains the next step for the LAN: a content-based signal like the asset flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The asset flags split read from write: "writes assets folder" (assets-write) for writeAsset, appendAsset, deleteAsset and openAssetStream opened to write. Writes only ever land in the assets folder and Local Image only reads, so "reads all files" stays as it was; "reads assets folder" no longer hides writes. A cache-home-only scene shows neither. - The "Update to latest" dialog reads the new version first (the store's scenes.json on the cloud, the template's scenes on the backend, catalog flags as the fallback, 3 s cap) and says what it gains, for the clicked scene and for "Update all scenes"; the button turns red when the clicked scene gains shell, files or writes. The update buttons say "Checking…" meanwhile. - sceneAccessChanges / describeGainedAccess in sceneAccess.ts and sceneUpdateAccessNotice in sceneUpdateAccess.ts, with a `changes` section in docs/scene-access-fixtures.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…el code goes What the frame enforces is what each app asks for in its config.json `permissions`, the way `settings` already gates getSetting: - JsAssetGrant (one byte per JS app runtime, parsed once in initDynamicJsApp): no "assets"/"files"/"assets-write" keeps the app's asset calls in the scene's own <assets>/scenes/<sceneId>/; "assets" or "files" reads the frame's folder and cannot change it; "assets-write" reads and writes it. No `permissions` key (apps from before 2026.10.16) runs as before. The write check is one condition in resolveAssetPath, which every caller already handles, so jsAssets gains no exits (each exit repeats the cleanup of all its live strings). - Built-in Nim apps declare theirs statically (Local Image "files", chromiumScreenshot and rstpSnapshot "shell"); they are our code and are not checked. - Gone: settings.assetAccess and its parse hook, asset_scope.nim, the Local Image confinement and the saveAsset redirect. localImage, apps.nim, config.nim, types.nim and interpreter.nim are back to main apart from the storeOrigin removal. ESP32 cost, measured on the generated C (host -Os object): app_runtime +868 B text / +112 B data against main; scene_guard (~500 B) drops out of the ESP32 build, and fos_scenes.c loses its per-load strstr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ore fonts
Permissions belong to apps: each app's config.json `permissions` (shell,
files, assets, assets-write), and a scene shows the union, plus the API keys
its apps read ("uses OpenAI key", `key:<namespace>`).
- Editor: sceneAccess.ts reads apps instead of scenes. Built-in apps declare
statically (Local Image files, the two shell apps shell, checked against
their config.json). A JS app's list follows its code on every save
(withAppPermissions in sanitizeScene, the cloud scene saves, template
save/publish and the wasm preview payload) unless it says
"permissionsAuto": false. The scene settings panel lists what each app asks
for; tags, the store, the URL-install dialog and the update dialog include
the key flags. The scene-level settings.assetAccess is gone.
- Frame: asking for nothing gets nothing. An app with no list keeps its asset
calls in the scene's own folder (JsAssetGrant: agUndeclared removed; the
runtime constructor defaults to it too).
- Self-hosted backend: migration 5e2a9c71d4b8 writes every stored JS app's
list from its code (frames, their deploy baseline, templates), with a
frozen Python copy of the detection that replays the shared stamp corpus.
- Cloud: store versions stay immutable; the push stamps missing lists for a
firmware from APP_PERMISSIONS_FROM (2026.10.16), and the hub's
firmware-change resync now also fires across that version, so a frame gets
its lists, on the scene it shows, the moment it starts enforcing them. The
scenes.json route and zip download stamp always.
- Lint: config.json permissions must be the four names; a hand-set list short
of the code is a warning. Legacy Nim code shells out whatever the node
calls itself; JavaScript code never does.
- Store: category headers text-lg -> text-2xl, scene names text-sm ->
text-base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rom its code A JS app's config.json `permissions` now always follows its code. A list written by hand is replaced on the next save; if a manual grant is ever needed it can be a permission of its own in the list. The AI linter's shortfall warning covers every app with a list (it only covered hand-set lists before), so a stale list in a scene saved outside the editor still reaches the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Threat
A scene from someone else (the scene store, a URL, a repository) is anyone's code. Until now:
data/localImagecould name any folder on disk;The first version of this PR confined store-origin scenes by provenance (
origin.storeSceneId). That bit is hard to keep true across copies, forks and imports, so the frame no longer reads it anywhere. Permissions are now part of the content, declared by apps.Apps ask, scenes add up
Each app's
config.jsonhas apermissionslist with four names. A scene asks for exactly what its apps ask for.assetslistAssets,readAsset,loadAssetImage, a stream openedr, ...assets-writewriteAsset,appendAsset,deleteAsset, a stream openedw/afilesshellsettingsnamespace an app reads appears as "uses OpenAI key" (key:openAI).shell.config.json.BUILTIN_APP_ACCESSinfrontend/src/utils/sceneAccess.tsmirrors them, with a test against everyconfig.json.JsAssetGrantinjs_runtime/app_runtime.nim, read once fromconfig.json, likesettingsgatesgetSetting):assets/files/assets-write, or no list at all: the scene's own<assets>/scenes/<sceneId>/, read and write;assetsorfiles: read the frame's assets folder, no writes;assets-write: read and write it.fetchImage({saveAsset})is frame caching and stays insaved/.js.assetSandbox: "scene"still confines every app.Where it shows
risk_flagsare computed at publish with the same detector, keys included.permissionslist may only use the four names.docs/scene-access-fixtures.jsonis the corpus: scenes, sets, changes, and stamp cases that pin the exactconfig.jsonoutput.Existing scenes keep working
5e2a9c71d4b8writes every stored JS app's list from its code: frames, their deploy baseline, and templates. It uses a frozen Python copy of the detection that replays the shared stamp corpus. Lists only appear where missing, and hand-set lists and unparseable files are left alone. A frame gets the lists with the deploy that installs the release that reads them.APP_PERMISSIONS_FROM(2026.10.16).scenes.jsonroute and zip download stamp always.Shell apps and the LAN deny drop provenance too
shellAppsceremony to run the two process-spawning apps. Theirshellpermission is shown, and installing approves it.shellAppsnow only lets Chromium run unsandboxed as root.ESP32 size
The frame side, against main, is +172/−289 lines. The scene-level code (
settings.assetAccess,asset_scope.nim, the Local Image confinement, thesaveAssetredirect) is gone.Measured on the generated ESP32 C compiled at
-Oson the host:app_runtime: +868 B text and +112 B data. The write check is a single condition inresolveAssetPath, so no new exits repeat ORC cleanup.scene_guard(about 500 B) drops out of the ESP32 build.fos_scenes.closes its per-loadstrstr.Also
Store page: category headers
text-lg→text-2xl, scene names in cardstext-sm→text-base.After deploy
Run
pnpm --filter @frameos-cloud/auth-web scene:backfill-risk-flags --applyto recompute existing store versions' tags, keys included. It is a dry run without--apply.Not in this PR
docs/security-todo.md: a declaredlanpermission.Tests
test_js_app_runtime: per-app grants, no list, non-list, and the frame-folder tests asking explicitly;nim checkfor Linux, ESP32 and wasm.test_app_permissions_migration.py(corpus parity, streams, inline and non-JS apps, frames + baseline + templates);pnpm verify(28/28);tsc,kea-typegen check, prettier, and the shared-spascene-access(62) and update tests.🤖 Generated with Claude Code