7.6 KiB
App launch and signer trust boundary
This note describes the current safe launch candidate. It preserves the existing Android Companion app overlay for ordinary apps and the dashboard's embedded AppSession iframe in a browser. A manifest's requires_host_frame flag is a deliberate exception, not a blanket migration of phone apps into dashboard frames. The mobile node-signer experience still needs physical acceptance before that policy changes.
Where an app runs
neode-ui/src/stores/appLauncher.tsselects the launch surface. In Companion, an ordinary app usesopenInAppOrNewTaband the nativeopenInAppEx/openInAppoverlay. In a browser, an embeddable app stays inAppSessionand its iframe; apps whose manifest saysopens_in_new_tabuse a separate tab. A deep link and the app's visible back/close controls must survive either path.scripts/generate-app-catalog.pyderives launch ports, new-tab status, andrequires_host_framefrom app manifests.generatedAppSessionConfig.tsis generated output; change the manifest and regenerate it rather than adding a broad frontend override. In the current safe candidate,justworksandkithmootrequire the host frame. GitWorkshop, Blossom, Gashboard, IndeeHub, and podsteadr retain the existing Companion native overlay while mobile signer parity is unresolved.- The browser iframe lives in
AppSessionFrame.vue. A cross-port app keeps its own origin and storage. A same-origin/app/...fallback is sandboxed withoutallow-same-origin; it cannot read dashboard cookies, DOM, or CSRF state. The sandbox is tied to the current iframe URL and is recalculated when navigation changes origin.allowgrants clipboard write to the frame and only catalog-verified extra permissions; fullscreen is not implicit.
Signer and app origin
- The dashboard's
useNostrBridge.tsaccepts a signer request only from the exact mounted frame window and the expected app origin. For a same-origin sandboxed frame, the sender origin isnull; frame-window equality and the expected same-origin URL are both required. Every sign request still requires the existing identity and consent checks. Do not replace these checks with a wildcard origin, relaxed CORS, or automatic signing. - The known node-provider consumers are GitWorkshop (
archipelago-source), Blossom, Gashboard, IndeeHub, Kithmoot, and podsteadr. Some also use media, registration, or payment bridges. Their provider script and session must be tested on the surface that actually launches them; the presence ofnostr-provider.jsalone does not prove phone signer behavior. - The safe phone candidate keeps those apps in the native overlay. Its proposed
ArchipelagoSignertransport belongs only to the verified app main frame at the exact app origin. A separate, visible node-origin/nostr-signer?native=1broker usesArchipelagoSignerHost; the app cannot inspect that broker's DOM or receive its node credentials. The native listener must bind each request to the app ID, origin, current URL, navigation generation, and one session. It must deliver INIT, await ACK, and cancel pending work on navigation or close. These are requirements for the Companion implementation and physical acceptance, not proof that the installed Android app already enforces them. - The broker's source allowlist maps the six provider consumers to their declared app ports: IndeeHub 7778, Blossom 8191, GitWorkshop 8337, Gashboard 1337, podsteadr 8095, and Kithmoot 8189. The browser-tab provider keeps its existing narrow identity hints. The ordinary browser iframe continues to use exact parent-window and origin checks; native messages do not weaken that path or bypass visible identity/consent decisions.
- GitWorkshop's prior same-origin
/app/archipelago-sourceiframe had an opaque sandbox origin (null) while its NIP-07 provider needed an app-bound signer context. The current browser URL uses the app-gate origin on port 8337 while retaining the compiled/app/archipelago-sourcerouter base. The gate owns only that app's provider route. Do not repair the opaque-origin failure by addingallow-same-originto dashboard-origin frames, exposing dashboard credentials to app JS, or broadening app-gate routes. The separate Companion overlay can lose the dashboard parentpostMessagebridge; that mobile signer path remains an acceptance gate, not a source-test pass. - GitWorkshop's provider script URL is versioned in its built HTML. A past SPA fallback at the old URL can remain in a phone's HTTP cache as
text/htmland silently suppresswindow.nostrundernosniff. Bump the URL when repairing that state; the app gate must serve the exact root provider route as JavaScript withCache-Control: no-store. Qualify an upgrade in the same browser profile without clearing storage or cache. - Native signer INIT only binds the verified app origin and acknowledges the session; it must not send a remembered identity before queued requests, because that can complete an explicit account choice without user selection. An app that offers login on launch should issue one ordinary NIP-07 login request after its UI mounts, guarded against duplicate mounts and skipped when its own session is active. A remembered node selection can then satisfy the public-key request; otherwise the broker shows the visible chooser. Signing still follows the normal consent decision. Browser-tab and dashboard-iframe startup keep their own existing paths. Qualify a fresh native launch, remembered-session reopen, cancellation, account switch, and actual signed return before accepting an app's automatic login UX.
Pine's two-port rule
Pine launches through its gated HTTP entry port 10380. That entry redirects to its HTTPS UI on 10381 so Web Bluetooth has a secure context. The second port is admitted into FIPS only when the manifest declares a matching HTTPS UI interface and a public TCP auth: none mapping for the same host port. It is not a general exception for arbitrary service ports or CORS. scripts/tests/test_generate_app_catalog_ports.py checks the generated FIPS port list and rejects a UDP, local-only, mismatched, or non-HTTPS declaration. Preserve the redirect and certificate behavior in real browsers.
Regression gates before changing launch policy
- Desktop browser: verify direct per-app origin and router paths, signer identity/consent, back/forward/close, initial loading/error/success, and no extra iframe reload on panel/overlay changes. Check the same-origin fallback remains opaque. Run at phone and desktop widths for any UI change.
- Android Companion: verify the existing native overlay opens and closes with its phone controls, no speed or visual regression, and deep links/media still work. On both phones test exact-origin INIT/ACK, visible identity and sign consent, response delivery, navigation/close cancellation, and rejection of a different app origin or stale session. Do not infer success from desktop iframe tests or source-only tests.
- IPv6 and HTTPS: verify bracketed IPv6 host URLs keep the app port and expected scheme, app-gate TLS/certificate trust, GitWorkshop's
/app/archipelago-sourcebase, and Pine's 10380→10381 redirect. A certificate error inside an iframe is a failure, not an invitation to switch to mixed content. - Provider and security: verify exact frame source/origin binding, explicit consent, app-gate route scope, manifest-generated launch policy, and no broad sandbox/CORS changes. Run the focused
AppSessionFrameAuthority,appSessionConfig, app launcher, and catalog-port tests after changes.
Source tests protect routing and policy invariants. They do not replace installed desktop, mobile, or physical signer acceptance.