Security
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:
-
Trusted Types — wraps
the orchestrator's two DOM sinks (the injected
<script type="importmap">and the dynamicimport()call) in a vetted policy so raw strings never reach a script-execution sink. -
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:
-
createScript— re-parses the input and verifies it is a valid import map (a plain object with onlyimports,scopes, orintegritykeys). Rejects anything else with aTypeError. -
createScriptURL— verifies the input parses as a URL with anhttp:orhttps:protocol. Rejectsjavascript:,data:, and malformed URLs.
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:
-
With
useShimImportMap()(es-module-shims) theintegrityblock is fully enforced today — modules with a mismatched hash fail to load. -
With
useDefaultImportMap()(native browserimport()) theintegrityblock is honored by browsers that have shipped the import-mapintegritykey — it is part of the import-maps spec, with browser support rolling out. On older browsers the key is silently ignored, so the import map remains backwards-compatible.
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
- The orchestrator security docs — the upstream version of this page.
-
Configuration — host —
manifestIntegrityandhostRemoteEntry.integrityoptions. -
Configuration — import map
— the
trustedTypesPolicyNameoption. -
Architecture — generated import map
— where the
integrityblock is emitted into the DOM. -
Getting Started — the manifest
object form (
{ url, integrity }) andinitRemoteEntrywithRemoteRef.