Security

Applies to

Two complementary security features baked into the orchestrator: a Trusted Types policy that wraps every DOM sink, and Subresource Integrity verification across the manifest, every remoteEntry.json and every JavaScript module.

The orchestrator ships two complementary security features:

  1. Trusted Types — wraps the orchestrator's two DOM sinks (the injected <script type="importmap"> and the dynamic import() call) in a vetted policy so raw strings never reach a script-execution sink.
  2. Subresource Integrity (SRI) — verifies the bytes of the manifest, every remoteEntry.json, and every JavaScript module the import map points at against the SHA hashes published by the build.

The two features layer cleanly: Trusted Types makes sinks structurally safe, while SRI makes the data flowing through them tamper-evident.

Trusted Types

The orchestrator is compatible with the browser Trusted Types API. When the host application enables Trusted Types via Content Security Policy, the orchestrator's two DOM sinks — the <script type="importmap"> it injects and the dynamic import() it uses to load remote modules — flow through a vetted policy instead of writing raw strings.

What the orchestrator protects

The library has two places where externally-fetched data reaches a script-execution sink:

Sink Source of the data Trusted type
<script type="importmap"> content (replaceInDOM) Resolved import map built from remoteEntry.json files TrustedScript
Dynamic import(url) (loadModuleFn) Module URLs declared by remotes TrustedScriptURL

Both go through a single Trusted Types policy named nfo by default. The policy applies two checks:

On browsers without Trusted Types (and in test environments such as jsdom) the wrapper is a transparent pass-through, so the library behaves identically to a non-Trusted-Types build.

Recommended CSP

For a host that wants Trusted Types enforced and uses the orchestrator's defaults:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types nfo

If the host registers its own policies in addition to nfo, list them all (and add 'allow-duplicates' if a name is registered more than once):

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types nfo my-host-policy 'allow-duplicates'

Phased rollout

Trusted Types is best deployed in stages. Start in report-only mode to surface every violation without breaking the page:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; trusted-types nfo; report-uri /csp-reports

Once the report stream is empty, switch to the enforcing header above.

Configuration

The Trusted Types policy is part of the import-map configuration and exposes a single option, trustedTypesPolicyName.

import { initFederation } from '@softarc/native-federation-orchestrator';

initFederation('http://example.org/manifest.json', {
  // default
  trustedTypesPolicyName: 'nfo',
});

Renaming the policy

If your CSP trusted-types allowlist uses different names (for example to keep a project-wide naming convention), rename the orchestrator's policy:

initFederation('http://example.org/manifest.json', {
  trustedTypesPolicyName: 'my-app-nfo',
});
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types my-app-nfo

Opting out

Set the option to false when the host already wraps the import map and module URLs through its own policy (for example by overriding setImportMapFn and loadModuleFn):

initFederation('http://example.org/manifest.json', {
  trustedTypesPolicyName: false,
  setImportMapFn: myHostsTrustedSetImportMap,
  loadModuleFn:   myHostsTrustedImport,
});

With trustedTypesPolicyName: false the orchestrator will not call trustedTypes.createPolicy at all — useful when the CSP allowlist does not include nfo or when a default policy is already in place site-wide.

Important: policy ≠ sanitizer.
Trusted Types makes sinks structurally safe by funnelling all writes through a single, auditable choke point. The validators shipped with the orchestrator only check shape (JSON structure, URL protocol) — they are not a substitute for end-to-end controls on which manifests and remote origins your host trusts in the first place.

Subresource Integrity

The orchestrator can verify the bytes of every artifact it touches against an SRI-style hash before they are used. Verification is opt-in per resource: provide a hash and the bytes are checked, omit the hash and the resource is fetched as-is. This mirrors how the browser's <script integrity="…"> attribute works.

What can be pinned

Resource Where the hash lives Verified by
manifest.json manifestIntegrity option on initFederation Manifest provider hashes the response bytes before parsing
Each remoteEntry.json integrity field on the manifest entry, or hostRemoteEntry.integrity Remote-entry provider hashes the response bytes before parsing
Every shared external, exposed module, and chunk file integrity map on remoteEntry.json (emitted by @softarc/native-federation when the build enables features.integrityHashes) Browser / es-module-shims honors the integrity block of the generated import map

These three layers form a trust chain: pinning the manifest pins which remoteEntry.json files are trusted, each of which pins the modules the page actually executes.

Module & chunk integrity (import map)

When @softarc/native-federation is built with features.integrityHashes: true, the generated remoteEntry.json carries a top-level integrity map keyed by outFileName:

{
  "name": "team/mfe1",
  "exposes": [{ "key": "./Button", "outFileName": "button.js" }],
  "shared": [
    { "packageName": "react", "outFileName": "react.js", "version": "18.2.0" }
  ],
  "chunks": { "browser-react": ["chunk-ABCD1234.js"] },
  "integrity": {
    "button.js":          "sha384-…",
    "react.js":           "sha384-…",
    "chunk-ABCD1234.js":  "sha384-…"
  }
}

The orchestrator copies these hashes onto every URL it emits and writes the result into the integrity block of the import map it injects into the DOM:

<script type="importmap">
{
  "imports": {
    "team/mfe1/./Button": "https://mfe1.example.org/button.js",
    "react":              "https://mfe1.example.org/react.js"
  },
  "scopes": {
    "https://mfe1.example.org/": {
      "@nf-internal/chunk-ABCD1234": "https://mfe1.example.org/chunk-ABCD1234.js"
    }
  },
  "integrity": {
    "https://mfe1.example.org/button.js":         "sha384-…",
    "https://mfe1.example.org/react.js":          "sha384-…",
    "https://mfe1.example.org/chunk-ABCD1234.js": "sha384-…"
  }
}
</script>

URLs without a hash are simply omitted from the integrity block (matching the SRI spec).

Enforcement depends on the import-map runtime:

Pinning remoteEntry.json and manifest.json

remoteEntry.json and manifest.json are fetched as JSON, not as scripts, so SRI's <script integrity> attribute does not apply. The orchestrator instead computes a SHA-256 / SHA-384 / SHA-512 digest of the response bytes itself (crypto.subtle.digest) and compares it to the hash you provide. A mismatch rejects with an NFError.

Per-remote integrity in the manifest

A manifest entry can be either the existing string form or a { url, integrity } object — they coexist freely:

{
  "team/mfe1": "https://mfe1.example.org/remoteEntry.json",
  "team/mfe2": {
    "url": "https://mfe2.example.org/remoteEntry.json",
    "integrity": "sha384-…"
  }
}

Manifest URL integrity

When the orchestrator fetches the manifest itself from a URL, pass manifestIntegrity to verify it:

import { initFederation } from '@softarc/native-federation-orchestrator';

initFederation('http://example.org/manifest.json', {
  manifestIntegrity: 'sha384-…',
});

Host remote-entry integrity

The host's own remoteEntry.json is configured separately and supports the same integrity field:

initFederation(manifest, {
  hostRemoteEntry: {
    url: './host-remoteEntry.json',
    integrity: 'sha384-…',
  },
});

Dynamically added remotes

initRemoteEntry accepts the same RemoteRef shape, so a dynamically added remote can also be pinned:

const { initRemoteEntry } = await initFederation(manifest, { /* … */ });

await initRemoteEntry('http://example.org/late-mfe/remoteEntry.json', {
  name: 'team/late-mfe',
  integrity: 'sha384-…',
});

See also