Heimdall apps follow the webview-app boundary: the web layer cannot touch the
OS directly — everything privileged goes through a command you register in Odin.
That boundary holds only if your frontend is trusted code. Loading untrusted or
remote content into the webview breaks the model (that content can call any
registered command and drive the win window-control service). Keep the frontend
your own.
Scope: the bridge/IPC, asset serving, the CLI, the installers, and deep linking.
F1 — Shell injection in heimdall dev via dev_url (FIXED).
cli/cmd_dev.odin:free_port interpolated the port (parsed from dev_url in
heimdall.toml) into /bin/sh -c "lsof … kill" without validation. A malicious
heimdall.toml (e.g. dev_url = "http://x:1;<command>") would run an arbitrary
command when someone ran heimdall dev on that project. Fixed: the port is
now rejected unless it is purely numeric before any shell use.
F3 — Unbounded recursion on deeply-nested request JSON (FIXED).
Found by the bridge fuzz tests (heimdall/bridge_test.odin). core:encoding/json
parses recursively, so a deeply-nested request ([[[[…]]]]) overflowed the stack
and crashed the process — a DoS reachable from the webview (e.g. via an XSS in the
frontend). Fixed: an O(n) pre-scan (within_limits) rejects a request before
parsing if it exceeds 16 MiB or nests past 200 brackets (well above any real
command args). The guard is applied at both JSON entry points: parse_request
and parse_native_message — the latter is the actual native delivery path (the
backends' message handler parses the raw {i,n,a} envelope first, which initially
bypassed the guard until extended fuzzing caught it). All three native backends
now share that one guarded parser.
F2 — Installer download integrity (FIXED).
The release workflow now publishes a SHA256SUMS asset (computed over every CLI
binary + the framework tarball), and both installers download it and verify each
file's SHA-256 before installing — aborting on any mismatch. HEIMDALL_SKIP_VERIFY=1
opts out (not recommended). Still recommended later: signing/notarizing the
released CLI binaries themselves (the checksums protect integrity, not provenance).
- Bridge reply does not allow
evalinjection. Odin replies by eval-ingwindow.heimdall._resolve(<id>, <result>). Both<id>and<result>arejson.marshaloutput (the id is re-serialized from the wire message), so they are always valid JS literals — a craftedid/payload cannot break out of the call. The bridge fuzz tests (heimdall/bridge_test.odin) feed ~5,000 random + hostile inputs (including eval-breakout attempts) through the real inbound path and confirm it never crashes and always replies exactly once. - Event payloads + deep-link URLs are JSON-encoded before delivery to JS
(
emitmarshals the payload), so they cannot inject script. The reject path is fuzzed too:reject_jsonre-parses as valid JSON for thousands of hostile + invalid-UTF-8 inputs, andurl_matches_scheme/deliver_open_urlnever crash on craftedmyapp://…URLs. - No path traversal in asset serving. The
app://scheme handlers (and the unused loopbackserver.odin) resolve paths against the in-memory embeddedASSETSmap — there is no filesystem access, so../cannot escape.
heimdall dev/buildrun the project's configureddev_cmd/build_cmd(fromheimdall.toml), likenpmscripts or a Makefile. Running these on an untrusted project executes its build commands. Inspect a cloned project'sheimdall.tomlbefore running.- Deep-link URLs are untrusted input — anyone can invoke
myapp://…. Validate the URL in youron_open_urlhook /open-urlhandler before acting on it. - The
winservice is callable from any page script (window minimize/close/…). Intended for your own UI; another reason to keep the frontend trusted.
Pre-release, no formal process yet. Open an issue (or contact the maintainer) for anything sensitive.