Apps had wada.store, a key/value table flushed once on exit. Anything larger --
a log, a track, an exported CSV -- was impossible. wada.fs adds read/write/
append/list/remove behind CAP_LUA_SDK_EXT.
Two things make this safe rather than just possible.
SCOPE. Every app gets exactly one directory, /apps/<id>.d/, and cannot address
anything outside it. Store apps are user-submitted, so the name check is a
security boundary: no '/', no '..', no leading dot, strict [A-Za-z0-9._-], 32
chars. It REJECTS rather than sanitises -- a sanitiser that "fixes"
../../identity into something valid is how these go wrong. Checked against a
traversal list including '../identity', '/etc/passwd', 'sub/../x' and an
embedded NUL.
RATE LIMIT. This is the API where an app can do the thing that has twice taken
this firmware down: on internal flash a burst of small writes triggers garbage
collection, and a GC pass suspends the flash cache and stalls BOTH cores (#222,
and the beta_25 bootloop). deploy/apps/README.md already asks authors not to
write in a loop; asking is weaker than enforcing, so writes are capped at one
per second per app and return `false, "too fast"` otherwise. The write itself
runs under WdtHeavyGuard, so a pass that DOES trigger GC stalls briefly instead
of rebooting. Reads are capped at 32 KB so an app can never be handed an
unbounded buffer.
All 8 S3 envs build. Gate re-verified by symbol: fsWrite/fsWriteCommon exist
only in the T-Deck image, not the V4 TFT one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>