Skip to content

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

Description

@slabbdev

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…) without onNavigate ever 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 onNavigate anyway.

3. tinyjs update = curl https://tinyjs.app/install | sh

Same 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-run that prints instead of piping to sh.

4. setup.sh: predictable /tmp paths ($$-based) → symlink race

setup.sh builds temp paths from the PID, predictable in advance; a local user
can pre-place symlinks and swap files mid-install. Code-read. Fix: mktemp -d.

5. api-gate edges

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>/AITOOL event line from a compromised bridge would be
accepted. 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)

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()/
NSTask with page data in any launcher · osascript fallback argv-only · Windows
toasts 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.makeTempDir mode bits, txiki's TLS stack (mbedtls), tinyjs.app itself.

Activity

  1. tarwin commented on Oct 3, 2026

    @tarwin
    Owner

    Thanks @slabbdev — item 4 (setup.sh temp paths) and part of 7 (fetch streams keyed by window, \r in titles, null-prototype store) are fixed in v0.45.0. Leaving this open for the rest.

  2. tarwin commented on Oct 4, 2026

    @tarwin
    Owner

    Thanks again @slabbdev. v0.47.0 covers most of the rest:

    • 1 tiny-media:// proxy: serves only the app's own origins (or "api".origins keys allowing media.proxy), refuses document loads, and sends CSP sandbox. macOS + Linux.
    • 2 Other URL schemes now reach onNavigate in any frame, on all three OSes, and are denied unless it returns 'external'.
    • 3 tinyjs update runs 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_DEBUG redacts 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 to file:// are all blocked; probe in test/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).

    Leaving this open for 6 (launcher-side line hardening).

  3. tarwin commented on Oct 4, 2026

    @tarwin
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions