Тема
Безопасность Module Federation
Страница предполагает понятный bootstrap, созданный host runtime и знание participant scope. Общие ошибочные решения собраны на странице Анти-паттерны.
Модель доверия
MF remote считается отдельным и потенциально недоверенным participant. Даже если его разрабатывает ваша команда, отдельный deployment может содержать несовместимую или скомпрометированную версию.
Host обязан контролировать:
- URL и версию remote entry;
- runtime compatibility;
- tenant/user identity;
- разрешённые query и mutation namespaces;
- API-контракт;
- mount/unmount lifecycle.
Identity задаёт host
ts
const ordersScope = runtime.createParticipantScope({
participantId: "orders-remote",
participantType: "remote",
tenantId: authenticatedSession.tenantId,
userId: authenticatedSession.userId,
capabilities: {
queryNamespaces: [["orders"]],
mutationNamespaces: ["orders"],
},
})Не берите tenantId, userId, capabilities или remote URL из query string, localStorage remote либо данных, переданных самим remote.
Least privilege namespaces
Разрешайте минимальные префиксы:
ts
capabilities: {
queryNamespaces: [
["orders", "list"],
["orders", "details"],
],
mutationNamespaces: [
"orders.cancel",
"orders.repeat",
],
globalInvalidation: false,
}Пустой список означает запрет. globalInvalidation нужен только доверенному composition layer; обычному remote он не требуется.
Transport не входит в scope
Scope создаёт stores и ограничивает cache operations. Он не содержит Axios instance, token provider или произвольный execute(url).
Remote получает отдельный typed API contract:
ts
export interface OrdersApi {
list(config?: AxiosRequestConfig): Promise<AxiosResponse<Order[]>>
cancel(id: string, config?: AxiosRequestConfig): Promise<AxiosResponse<Order>>
}Host может реализовать adapter поверх authApi. Remote вызывает adapter только из своего business store.
Private cache по умолчанию
Каждый mount получает уникальный instanceId. Private query одного mount не доступна другому mount, даже если совпадают participantId и logical key.
Shared namespace включайте только для данных, безопасных для всех разрешённых participants одной tenant/user partition:
ts
sharedQueryNamespaces: [["shared", "currency"]]Не делайте shared cache для permissions, персональных черновиков, токенов и платёжных данных.
Remote entry
- загружайте remote только с allowlist origins;
- используйте HTTPS;
- задайте CSP для script origins;
- публикуйте chunks с content hash;
- логируйте фактическую версию remote;
- отклоняйте неизвестный protocol до mount;
- не передавайте secrets через bootstrap options.
Unmount и logout
При unmount:
ts
unmountRemote()
ordersScope.dispose()При logout host сначала запрещает новые private operations, затем освобождает remote scopes, plugins и root runtime. После смены пользователя создаётся новый runtime. Cache старой user partition нельзя переиспользовать.
Что проверять в CI
- несовместимая версия отклоняется до mount;
- запрещённый namespace не запускает HTTP;
- два mount не читают private cache друг друга;
- shared query доступна только разрешённым participants;
- unmount обнуляет owned stores и requests;
- remote не импортирует private transport host;
- bootstrap contract не содержит credentials.