Repository navigation
Hardening batch: unauthenticated tiny-media:// CORS-bypass proxy, navigation-scheme bypass, tinyjs update = curl | sh, setup.sh /tmp races, api-gate edges — plus the areas this pass verified clean #30
Copy link
Copy link
Closed
Description
Activity
- added 4 commits that reference this issue
on Oct 3, 2026 - added 13 commits that reference this issue
on Oct 4, 2026 Thanks again @slabbdev. v0.47.0 covers most of the rest:
- 1
tiny-media://proxy: serves only the app's own origins (or"api".originskeys allowingmedia.proxy), refuses document loads, and sends CSPsandbox. macOS + Linux. - 2 Other URL schemes now reach
onNavigatein any frame, on all three OSes, and are denied unless it returns'external'. - 3
tinyjs updateruns the installer shipped inside the install rather than piping tinyjs.app, and has--dry-run. - 5
*in origin keys is documented. The "Windows/Linux don't attest an origin" part was already out of date: every launcher stamps one now. - 7
TINYJS_DEBUGredacts clipboard and keychain traffic, and the docs drift is fixed.- Linux universal file access: left as is. WebKit applies it only to
file://documents, and a non-app page can't become one (fetch, iframe and navigation tofile://are all blocked; probe intest/file-access/). - Windows
UncHandler: it lives exactly as long as its webview. 60 window open/close cycles showed no handle growth. - Update hash from the same origin as the zip: split into Signed app updates (update zip hash comes from the same host as the zip) #40 (signed updates).
- Linux universal file access: left as is. WebKit applies it only to
Leaving this open for 6 (launcher-side line hardening).
- 1
- added 6 commits that reference this issue
on Oct 4, 2026 Thanks @slabbdev. Item 6 turned out to be more than defence in depth: the page chooses part of each call id, and the launchers wrote it into the backend line unchecked. A newline in it let a gated page add lines of its own, including a call stamped with the app's
file://origin, which got past the"api"gate. That was reproduced on all three OSes.Fixed in v0.47.1:
- Every launcher now requires the page part of a call id to be digits (on the Windows main window, the library's hex id) and drops the message otherwise.
- The line writers drop any line containing a raw CR/LF.
- The reply side (
RET) already insisted on numeric seqs before building page JS.
The probe is in
test/wire-lines/.With 1–5 and 7 in v0.47.0 and signed updates split into #40, that's everything here, so closing.
Reacted by Sam LABBE
Metadata
Metadata
Assignees
Labels
No labels
Last batch from the pass that produced #22/#24/#26 and the curl/sampler issues.
Mixed severities, mostly low individually — filing together so the tracker stays
readable; each item has a suggested direction. Verification status is marked
per item.
1.
tiny-media://proxy/?u=— CORS-bypass proxy without auth or origin check (mac+linux)The custom scheme handler proxies any URL passed as
?u=with no authentication,no origin allowlist and no content-type restriction — a page (including a wrapped
third-party one) can read same-origin-policy-protected content of any origin
through it, bypassing CORS entirely. Code-read, current main. Fix: restrict to
the app's own origins, or require a per-request token minted by the bridge.
2. Navigation policy is http(s)-only — custom schemes bypass
onNavigate(×3 OS)The navigation policy answers http(s); a page can navigate/iframe to custom
schemes (
intent:, registered app schemes…) withoutonNavigateever seeing it,defeating app-level navigation audits. Code-read; Windows launch behavior needs
a box. Fix: default-deny non-http(s) navigations unless the manifest allows the
scheme, and surface them to
onNavigateanyway.3.
tinyjs update=curl https://tinyjs.app/install | shSame supply-chain posture as #26 but for the updater itself: the install/update
command pipes a shell script straight from the website. The site is the trust
anchor for the whole machine here (TLS only). Code-read; matches the tool's
documented design ("defy design"), filing for the record. Fix: checksum-pinned
installer + a
--dry-runthat prints instead of piping to sh.4. setup.sh: predictable /tmp paths ($$-based) → symlink race
setup.shbuilds temp paths from the PID, predictable in advance; a local usercan pre-place symlinks and swap files mid-install. Code-read. Fix:
mktemp -d.5. api-gate edges
*compiles to an unbounded.*— a pattern likeuser*matchesuserfoo.bar.baz; fine if intended, worth documenting.originundefined today), the gate treats it as "no origin scoping":
origins-keyedgrants apply to every frame of those platforms. With Linux: origin attribution is frame-blind — a hostile subframe inherits the top frame's
apigate #18 fixed on Linux,that becomes the remaining per-origin gap on win/linux. Code-read.
6. Launcher-side
seq/event hardening (defense in depth after #23)#23 fixed the bridge sinks; the launcher still executes wire lines whose
seq/identifier fields arrive raw, and the reply matcher keys on them — a forged
MENU <id>/HOTKEY <id>/AITOOLevent line from a compromised bridge would beaccepted. Belt-and-braces: scrub line structure launcher-side (all three
launchers), the same way #23 scrubbed bridge-side. Code-read; needs native
rebuild to test.
7. Minor batch (each small, listed for completeness)
allow_universal_access_from_file_urlsis TRUE for all webviews (not just app pages) — interacts with Security: getUserMedia is auto-granted without an origin check (macOS unconditional hook; Linux manifest-only) — a wrapped third-party site gets mic/camera with no per-site consent #24's wrapped-origin concern.meta.originadvertised "engine-attested" but false on Linux.TINYJS_DEBUG=1traces clipboard/secrets to stderr.\rnot stripped (launcher parse quirk).storekeys pass through a__proto__-unsafe object spread.fetchStreamsmap has no owner check (any window can pull another's stream).UncHandlerregistered without unregister (handle retention).8. Areas this pass verified CLEAN (for the record — saves you a re-audit)
Menu/tray/clipboard/hotkey/keystroke/drop all escape correctly (
one()/esc():HKREG, CLIPWRITE, DRAGOUT, TRAYBEGIN, MENUBEGIN, DLG…) · no
system()/popen()/NSTaskwith page data in any launcher · osascript fallback argv-only · Windowstoasts fully xml_escape()d · keystrokes = keycode whitelist · dev socket
mkdtemp 0700, no race · store tmp+rename atomic, keys never in paths · curl body
temp 0700 with cleanup on all exits · Windows launcher is pipe-client-only, CALL
origin forging closed (position-from-end read) · audioTap gate really enforced in
the bridge · release workflows quoted, cache keys not influencable · template
scaffold innerHTML escaped.
Scope note: static pass over bridge.js, cli.js, the three launchers and the
update/install path as of
5bb2311(v0.42.3). Not covered:native/tiny_ai.swift,tjs.makeTempDirmode bits, txiki's TLS stack (mbedtls), tinyjs.app itself.