Skip to content

fix: prevent concurrent TURNKEY_INIT_MESSAGE_CHANNEL from establishing multiple channels#119

Open
leeland-turnkey wants to merge 2 commits intomainfrom
leeland/eng-3785-sb-2026-22-frames-channel-bootstrapping-allows-multiple
Open

fix: prevent concurrent TURNKEY_INIT_MESSAGE_CHANNEL from establishing multiple channels#119
leeland-turnkey wants to merge 2 commits intomainfrom
leeland/eng-3785-sb-2026-22-frames-channel-bootstrapping-allows-multiple

Conversation

@leeland-turnkey
Copy link
Copy Markdown
Contributor

@leeland-turnkey leeland-turnkey commented Apr 14, 2026

Description:

Fixes ENG-3785

This is from the Audit here: https://cantina.xyz/code/0b2d181e-b8dd-4aeb-bbff-23a5fe8be69d/findings/22

Problem

The TURNKEY_INIT_MESSAGE_CHANNEL bootstrap handler is async. If two
init messages arrive before the first await resolves (e.g. from a
legitimate parent and a malicious sibling iframe), both invocations can
pass the outer if check before turnkeyInitController.abort() is ever
called. This allows multiple origins to establish a message channel and
register a port — including overwriting the legitimate parent's port.

Fix

Add a channelEstablished boolean flag that is checked and set
synchronously before the first await. Because JavaScript is
single-threaded, this check-and-set is atomic: any concurrent invocation
will see the flag already true and return immediately, regardless of
whether the first handler is still suspended at an await.

Why both HTML and JS files are changed

The four frames split across two implementation patterns:

  • export-and-sign and import use modular source files
    (src/event-handlers.js, src/index.js) that are bundled by webpack.
  • auth and export embed their bootstrapping logic directly in
    inline <script> tags inside index.html / index.template.html.

The same vulnerability existed in all four, so the same fix was applied
to both patterns.

Tests

Three unit tests added to export-and-sign covering:

  1. Happy path — a single init message establishes a channel normally
  2. Concurrent race — a second message dispatched before the first
    await resolves is ignored (the core regression test)
  3. Late duplicate — a second message arriving after the first completes
    is also ignored

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant