Skip to content

Define a safe edge-CSP migration path for opaque app modules #114

Description

@hamzamerzic

Context

PR #109 reports a real total-availability failure after app modules moved to the parent-brokered blob loader:

  • the live platform code imports the transferred module through import(blobUrl);
  • an older or separately managed edge CSP still serves script-src without blob:;
  • every mini-app then fails with Failed to fetch dynamically imported module: blob:null/....

The current bundled Caddyfile has the required route-specific blob: policy, but that does not by itself make the migration safe for existing self-hosted installations.

Why this can affect the standard self-hosted update path

In docker-compose.yml, Caddy reads ./Caddyfile from the host checkout. The in-product platform updater advances /data/platform inside the app's persistent volume and rebuilds/restarts the served platform, but it cannot update or reload the host-mounted Caddy configuration.

That means an ordinary self-hosted instance can legitimately reach this skew:

  1. host Caddyfile remains from before the app-frame blob policy;
  2. Settings applies a newer live platform containing the blob-only loader;
  3. the old edge policy overrides the frame response CSP and blocks every app.

This is therefore an update/deployment compatibility gap, not only a custom-proxy misconfiguration.

Assessment of PR #109

The availability finding is useful, but the proposed direct-import fallback should not merge as written.

Valid concerns from the review:

  • a genuine top-level module evaluation failure can run twice: once from the blob, then again from the direct URL;
  • the second import bypasses the exact-parent broker's size, source/version and controlled offline path;
  • it omits the module v= key;
  • the app bearer appears in the imported URL, where browser-generated errors or console output may retain it;
  • the current static test forbids only the old import(moduleUrl(...)) spelling and does not exercise this renamed fallback;
  • current main now has isBlobModuleLoadFailure(), so any compatibility behavior must be placed after precise classification and a bounded retry rather than catching arbitrary evaluation failures inside importBrokeredModule().

One correction to the review: the patch's inner catch does not catch failures from requestModuleBytes(). That await occurs before its try, so broker network/HTTP/token-expiry failures do not enter the direct fallback. The double-evaluation and second-request contract problems still stand.

Architectural constraint

This should not be solved by restoring allow-same-origin on a shell-origin scripted frame. The opaque origin intentionally removes ambient owner authority: shell DOM access, shell storage and the owner JWT.

It also should not imply that every possible application must be recreated as a bespoke compatibility API. The current capability architecture already has the right split:

  • ordinary mini-apps use an opaque frame, an app-scoped principal and general, reviewable host capabilities;
  • reusable browser/device primitives belong in the capability broker as real apps require them;
  • full applications needing cookies, origin storage, their own service worker or broad web-platform behavior use a separate service origin/gateway;
  • privileged reusable integrations can become platform capability providers;
  • fully trusted behavior can become explicit platform code.

The goal is high capability without ambient shell authority, not a fake web platform inside the iframe.

A future generic host-mediated request primitive could also keep the app-scoped bearer in the host while preserving server-side app permissions, avoiding a bespoke wrapper for every Möbius API route. That is broader than this loader migration and does not replace lifecycle-aware browser capabilities or distinct origins for full services.

Desired outcome

Define and test an update contract in which a platform update cannot turn a valid existing self-hosted deployment into an all-app outage when the edge policy is older than the app-frame loader.

A solution should satisfy all of these:

  1. No total outage across the supported update path. Either verify the actual public frame CSP before activating a blob-only loader, or retain a safe compatibility path until the edge is updated.
  2. Clear operator recovery. Detect the missing blob: policy and explain how to update and reload the host Caddy/proxy configuration.
  3. No broad failure catch. Network, HTTP, token-expiry, syntax and app-evaluation failures must keep their existing deterministic paths; app code must never execute twice.
  4. Preserve the broker invariants. Exact frame/app attribution, installation identity, version key, size bound, token refresh and service-worker/offline behavior must remain intact.
  5. No bearer leakage through module URLs or telemetry. Prefer keeping credentials in the host-mediated path.
  6. Exercise the real migration. Add a test for an old bundled Caddy policy combined with the new live platform, rather than only asserting that the current Caddyfile contains blob:.

Possible directions

  • Add an update preflight/health check against the actual public /api/apps/{id}/frame response and stop with actionable guidance when its effective script-src cannot run the incoming loader.
  • Provide an explicit, deterministic host-side Caddy update/reload step as part of the self-hosted upgrade documentation or tooling.
  • If a code compatibility bridge is still needed, place it only after the exact blob-load classifier and bounded retry, and design it so it does not re-fetch/evaluate an unverified current module or expose the app token in an import URL.

PR #109 can be closed or substantially rewritten after this migration contract is chosen; its reproduced failure remains the evidence for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions