ADR 0117 — website/ получает собственный lockfile, вне pnpm audit продукта
- Статус: принят
- Дата: 2026-08-28
Контекст
website/ (сайт Docusaurus, публикующий docs/) является участником
корневого pnpm-workspace.yaml с момента своего создания, разделяя единственный
pnpm-lock.yaml с apps/api, apps/web, apps/runner, packages/* и
scripts. Ничто в website/ не попадает в production-образ — сайт
собирается и публикуется собственным workflow
(docs-deploy.yml/docs-check.yml) и никогда не упаковывается в артефакты api,
web или runner.
Общий lockfile взимал две цены, не имевшие ничего общего с продуктом:
pnpm auditперестал отвечать на вопрос «безопасен ли продукт?» и стал отвечать на «безопасны ли продукт и генератор документации?». Docusaurus,docusaurus-theme-openapi-docsи их транзитивное дерево (webpack, postman-collection, oas-validator,@redocly/*…) — это большая и быстро меняющаяся поверхность, которая никогда не поставляется, и каждый advisory в ней появлялся на том же шлюзеpnpm audit, от которого зависит продукт (ci.yml, job «Auditoria de dependências»).- Большинство overrides безопасности в
pnpm-workspace.yamlсуществовало только для исправления транзитивной зависимости сайта. Из 13 записей, проаудированных для этого решения,serialize-javascriptи диапазонyaml@1.xиспользовались исключительно деревом зависимостейwebsite/(подтверждено с помощьюpnpm why <pkg> -rпо общему lockfile, а не перечитыванием комментариев — см. «Что нашёл аудит» ниже). Держать их в корне означало нести поверхность уязвимостейwebsite/как будто она принадлежит продукту.
Что нашёл аудит
Запись backlog, предложившая это, предполагала, что разделение будет чистым: «overrides
website уходят, overrides продукта остаются». Запуск pnpm why <pkg> -r
по lockfile до разделения для 13 overrides показал
более запутанную реальность: несколько пакетов, которые комментарии приписывали только
website/, также подтягиваются apps/api или apps/web по цепочке,
отличной от описанной в комментарии:
| пакет | цепочка самого website | также требуется |
|---|---|---|
js-yaml (<4.3.1) | openapi-to-postmanv2 / @11ty/gray-matter | apps/api, через eslint → @eslint/eslintrc |
js-yaml (<5.2.2) | @redocly/openapi-core | apps/api, через @nestjs/swagger (уже известно) |
mermaid | @docusaurus/theme-mermaid | apps/web, прямая runtime-зависимость (ADR 0068) |
dompurify | через mermaid | apps/web, через тот же mermaid |
uuid | postman-collection, sockjs | apps/web, через mermaid |
postcss | postcss-preset-env (транзитивная от docusaurus-theme-openapi-docs) | apps/web, через собственную зависимость vite, postcss: ^8.5.17 |
nanoid | через собственную цепочку postcss | apps/web, через ту же цепочку postcss под vite |
fast-uri | @redocly/ajv | apps/api, через ajv → @angular-devkit/core → @nestjs/schematics |
lodash | @docusaurus/core, прямая | apps/api, через @nestjs/swagger (уже известно) |
undici | cheerio (локальный поиск) | apps/web, devDependency jsdom (уже известно — единственный случай, который запись backlog пометила как смешанный) |
Лишь два override оказались действительно исключительными для website
(serialize-javascript, yaml@1.x), и один оказался исключительным для продукта, без
какого-либо присутствия в дереве website/ (esbuild, через drizzle-kit).
Это важно для решения ниже: разделение lockfile не означает, что большинство overrides просто «переезжают». Ранее общий override, исправляющий пакет, присутствующий с обеих сторон, должен продолжать защищать оба дерева, когда они перестают разделять одно разрешение, — убрать его с одной стороны, потому что комментарий называл другую, тихо вновь открыло бы CVE на той стороне, которая его потеряла.
Решение
1. website уходит из списка packages: в pnpm-workspace.yaml
Он перестаёт быть участником workspace. pnpm install в корне репозитория
перестаёт трогать что-либо под website/, а
apps/api/apps/web/apps/runner/packages/*/scripts продолжают устанавливаться
ровно как раньше.
2. website/ получает собственный pnpm-lock.yaml, устанавливаемый независимо
pnpm install, запущенный внутри website/ (не pnpm --filter website из
корня — этот глагол требует быть участником workspace), разрешает и фиксирует
зависимости website/ самостоятельно, только по website/package.json.
3. Overrides разделены по тому, где уязвимый пакет действительно разрешается, а не по тому, какой комментарий его называл
- Исключительные для website (
serialize-javascript,yaml@1.x): удалены из корневогоpnpm-workspace.yamlи добавлены в новый блокpnpm.overridesвwebsite/package.json(синтаксис отдельного проекта — внутриwebsite/нетpnpm-workspace.yaml, поэтому ключoverrides:уровня workspace там не действует). - Смешанные (
js-yamlв обоих диапазонах,mermaid,dompurify,uuid,postcss,nanoid,fast-uri,lodash,undici): сохранены в корне и продублированы вwebsite/package.json. Два lockfile теперь разрешают эти пакеты независимо, поэтому каждому нужен собственный нижний предел. - Исключительные для продукта (
esbuild): остаются только в корне.website/никогда его не разрешает, поэтому запись там была бы мёртвым грузом.
Комментарии в обоих файлах говорят, в какую категорию попадает каждая запись, и
указывают на этот ADR, чтобы следующему человеку, который тронет одну из смешанных
записей, не пришлось запускать pnpm why с нуля, чтобы узнать, что другой стороне
нужно то же исправление.
4. Скрипты и workflows переходят с --filter website на --dir website
pnpm --filter <name> выбирает пакет по принадлежности к workspace —
когда website вне workspace, каждый скрипт docs:* в корневом package.json
(start, build, serve, clear) переходит на pnpm --dir website <script>, который
запускает pnpm так, как если бы он был запущен внутри этого каталога, участника или
нет. Имена скриптов, которые люди уже набирают (pnpm docs:build,
pnpm docs:start), не меняются.
docs-deploy.yml и docs-check.yml получают каждый второй отдельный шаг
pnpm install --frozen-lockfile, ограниченный website/
(working-directory: website), сразу после уже существующей установки в корне.
Установка в корне больше не достигает зависимостей website/, и
без этого второго шага шаг сборки упал бы из-за отсутствия node_modules.
Последствия
pnpm auditв корне теперь сообщает только о дереве зависимостей продукта. Docusaurus, его тема, плагин OpenAPI и их транзитивные пакеты исчезают со шлюза, который запускает job «Auditoria de dependências» вci.yml, — поверхность CVE, о которой он сообщает, сужается до того, что действительно поставляется.- Удобство единой установки закончилось. Изменение, затрагивающее
пакет продукта и
website/(редкое, но выводdocs:generateзатрагивает оба), теперь требует двух запусковpnpm install, одного в корне и одного внутриwebsite/, вместо одного.docs-deploy.yml/docs-check.ymlплатят ту же цену вторым шагом CI. - У смешанного override два дома, которые нужно держать синхронными. Если
apps/webоткажется отmermaid(илиviteсам подниметpostcssза пределы уязвимого диапазона), корневую запись можно будет удалить обычным образом (pnpm auditперестанет жаловаться), но копия вwebsite/package.jsonэтого не заметит, пока кто-нибудь не запуститpnpm auditвнутриwebsite/тоже — единой команды, которая проверяла бы оба дерева сразу, пока нет. - Правило
site-e-publicacaoвdocs/.docmap.ymlуже наблюдает заwebsite/**и.github/workflows/docs-deploy.ymlвwarn, поэтому будущее изменение в любом из них по-прежнему требует взглянуть наdocs/explanation/documentation-workflow.md, который теперь также фиксирует схему с двумя lockfile.
Отвергнутые альтернативы
- Оставить
website/в workspace и просто терпеть его CVE вpnpm audit: status quo. Отвергнуто, потому что весь смысл шлюза аудита — предупреждать команду, когда у поставляемого пакета есть известная уязвимость, а пакет темы Docusaurus топит этот сигнал в шуме, с которым никто ничего не может сделать, кроме как поднять зависимость инструмента документации. - Перенести
website/в отдельный репозиторий: решает ту же проблему и больше, но теряет источник документации (docs/остаётся в этом репозитории), живущий рядом с сайтом, который его отображает, а pipelinedocs:generate, читающему кодapps/apiнапрямую для сборки справочника OpenAPI, понадобился бы шаг между репозиториями. Вне рамок того, что просила запись backlog. - Держать все overrides в корне «на всякий случай»: проще написать и неправильно точно так же, как были неправильны исходные комментарии со смешанной атрибуцией, — скрывает, какой пакет действительно нуждается в защите на какой стороне, и следующее разделение (или следующий аудит этих записей) снова начинается с нуля.